# JQHTML v2 Complete Syntax & Instruction Format Specification ## Overview JQHTML v2 is a composable component templating language for jQuery that compiles to efficient JavaScript instruction arrays. This document defines the complete syntax specification and the instruction format that templates compile to. ## Critical Concepts ### The Instruction Array Pattern Templates compile to functions that return arrays of rendering instructions: ```javascript // Every template function returns this structure: function render(Component, data, args, content) { const _output = []; // Instruction array // ... push instructions ... return [_output, this]; // CRITICAL: Returns tuple [instructions, context] } ``` ### Why This Pattern Exists 1. **Deferred Execution**: Instructions can be processed when optimal 2. **Single DOM Write**: All HTML built as string, then one `$.html()` call 3. **Complex Attribute Handling**: Functions and objects can't go in HTML strings 4. **Component Coordination**: Parent/child relationships preserved through execution ## Template Syntax ### 1. Component Definition Components are defined using the `` tag: ```jqhtml ``` ### 2. $ Attribute System JQHTML uses the `$` prefix for passing data to components as `this.args`: #### Syntax Rules 1. **Quoted = Literal String** ```jqhtml $title="User Profile" → args.title = "User Profile" $expr="this.user" → args.expr = "this.user" (the string, not evaluated!) ``` 2. **Unquoted = JavaScript Expression** ```jqhtml $user=this.user → args.user = {actual user object} $count=42 → args.count = 42 $enabled=true → args.enabled = true $handler=this.onClick → args.handler = function reference ``` 3. **Parentheses for Complex Expressions** ```jqhtml $status=(active ? 'online' : 'offline') $data=({...this.user, modified: true}) $items=(this.data.items || []) ``` #### Runtime Behavior - All $ attributes become properties in `this.args` (NOT `this.data`) - Stringable values (strings/numbers) appear as `data-*` DOM attributes - Complex values (objects/arrays/functions) accessible only via `this.args` - Component constructor syncs stringable args to DOM #### Example ```jqhtml $title="User Profile" $count=42 data-role="admin" /> ``` Result in UserCard component: - `this.args.user` = {name: "Alice", id: 123} (object) - `this.args.title` = "User Profile" (string) - `this.args.count` = 42 (number) - `this.args.role` = "admin" (from data- attribute) DOM shows: `
` (no data-user since it's an object) ### 3. Expressions Output JavaScript expressions using `<%= %>` (escaped) or `<%!= %>` (unescaped): ```jqhtml <%= this.data.title %> <%!= this.data.rawHtml %> <%@= this.data.maybeUndefined %> <%!@= this.data.maybeUndefinedHtml %> ``` ### 3. Code Blocks Execute JavaScript code using `<% %>`. Regular JavaScript passes through unchanged: ```jqhtml <% const items = this.data.items || []; %> <% console.log('Rendering:', items.length); %> <% if (condition) { %>

True branch

<% } else { %>

False branch

<% } %> <% for (const item of items) { %>
<%= item.name %>
<% } %> ``` **Design Rationale**: JavaScript code blocks pass through directly into the generated function. This allows developers to use any JavaScript construct without parser limitations. ## Component Invocation ### Basic Component Usage Components are invoked using capital-letter tags: ```jqhtml Default content ``` ### Property Passing ```jqhtml
``` ### $ Attribute System JQHTML uses the `$` prefix as a shorthand for data attributes with special handling: #### General Case: `$foo="bar"` → `data-foo` ```jqhtml
{tag: ["div", {"data-user": currentUser, "data-theme": "dark", "data-count": 42}, false]} ``` **Runtime Behavior**: 1. The attribute is set via jQuery's `.data()` method: `$element.data('user', currentUser)` 2. For debugging visibility, if the value is a string or number, it's also set as a DOM attribute 3. Objects and arrays are stored in `.data()` but not visible in DOM #### Special Case: `$sid="name"` → Scoped IDs The `$sid` attribute has special handling for component-scoped element selection: ```jqhtml
``` **Compilation**: ```javascript // In render function (receives _cid parameter) function render(_cid) { const _output = []; _output.push({tag: ["div", {"id": "container:" + _cid}, false]}); _output.push({tag: ["input", {"id": "username:" + _cid, "type": "text"}, false]}); _output.push({tag: ["button", {"id": "submit:" + _cid}, false]}); // ... } ``` **Runtime Usage**: ```javascript class UserCard extends Component { init() { // Find scoped elements const $username = this.$sid('username'); // Returns $('#username:123') const $submit = this.$sid('submit'); // Returns $('#submit:123') $submit.on('click', () => { const value = $username.val(); // ... }); } } ``` #### Lexical Scoping of `_cid` Component IDs flow through lexical scope naturally: ```jqhtml
<%= content() %>
``` This happens because content functions capture their defining scope: ```javascript // ParentComponent render function render(_cid) { // Parent's _cid = 123 _output.push({comp: ["ChildComponent", {}, () => { // This arrow function captures Parent's _cid const _output = []; _output.push({tag: ["div", {"id": "slot-element:" + _cid}, false]}); // slot-element:123 return [_output, this]; }]}); } // ChildComponent render function render(_cid) { // Child's _cid = 456 _output.push({tag: ["div", {"id": "child-element:" + _cid}, false]}); // child-element:456 } ``` #### Attribute Value Interpolation Within quoted attribute values, `<%= %>` and `<%!= %>` perform string interpolation: ```jqhtml
{tag: ["div", {"title": "User: " + user.name}, false]} ``` **Design Rationale**: Inside attribute values, HTML escaping is inappropriate because we're building a JavaScript string, not HTML content. The JavaScript string serialization handles all necessary escaping (quotes, backslashes, etc.). This is semantically different from top-level `<%= %>` which outputs to HTML and requires escaping. ## Binding Syntax (v2) ### Property Binding with `:` The `:` prefix creates dynamic property bindings that are always evaluated as JavaScript expressions: ```jqhtml
``` **Compilation**: - `@event="handler"` → `{"data-on-event": handler}` - Handlers are JavaScript expressions (function references or inline functions) ### Binding Design Rationale 1. **Vue.js Familiarity**: Uses the same `:prop` and `@event` syntax as Vue.js 2. **Always Expressions**: Unlike `$` attributes which distinguish strings vs expressions, bindings are ALWAYS expressions 3. **Runtime Processing**: The runtime will need to handle these specially: - `data-bind-*` attributes set properties dynamically - `data-on-*` attributes attach event listeners 4. **Separate from `$` System**: Bindings serve a different purpose than data attributes ## Component Content & innerHTML (Primary Pattern) ### Using `content()` Function - The Most Common Approach **The vast majority of components will use the simple `content()` function to output innerHTML passed by parent components.** This is the primary, recommended pattern for component composition: ```jqhtml
<%= content() %>

This is the content

More content here
``` ### Common Patterns with `content()` ```jqhtml
<%= content() %>
<% if (this.args.title): %>
<%= this.args.title %>
<% endif; %>
<%= content() %>
<%= content() %>
``` ### How `content()` Works 1. **Parent passes innerHTML**: When invoking `innerHTML here` 2. **Template receives it**: The `content` parameter in render function contains the innerHTML 3. **Output with `<%= content() %>`**: Call the function to output the HTML where needed 4. **No special syntax needed**: Just regular HTML/components as children ### Important Rules - **No mixing**: If a component uses ANY slots (`<#slotname>`), ALL content must be in slots - **Most components don't need slots**: The simple `content()` pattern handles 95% of use cases - **Slots are for advanced scenarios**: Only use slots when you need multiple named content areas ## Slot System (Advanced Feature) **Note: Slots are an advanced feature for specific use cases. Most components should use the simpler `content()` pattern shown above.** ### When to Use Slots vs `content()` Use `content()` (recommended for most cases): - Single content area - Wrapping/decorating content - Simple component composition - Standard container components Use slots (advanced cases only): - Multiple distinct content areas (header/body/footer) - Complex layouts with specific placement - Data table templates with row/header/footer sections ### 1. Named Slots Pass content to specific slots using `<#slotname>` syntax: ```jqhtml <#header> ID Name Email <#row> <%= row.id %> <%= row.name %> <%= row.email %> <#empty /> ``` ### 2. Accessing Slots in Components (Advanced) For the rare cases where slots are needed, components can check for and render specific named slots: ```jqhtml
<%= content() %> <% if (content('footer')) { %>
<%= content('footer', { timestamp: new Date() }) %>
<% } %>
``` ## Instruction Format Reference ### Instruction Types Templates compile to three instruction types: 1. **`{tag: [...]}`** - HTML elements 2. **`{comp: [...]}`** - Component invocations 3. **`{slot: [...]}`** - Slot definitions ### 1. Tag Instruction ```javascript {tag: [tagname, attributes, self_closing]} ``` **Format:** - `tagname`: String - HTML tag name - `attributes`: Object - Tag attributes (may contain functions!) - `self_closing`: Boolean - Whether tag self-closes **Examples:** ```javascript {tag: ["div", {"class": "user-card"}, false]} {tag: ["img", {"src": "/avatar.jpg", "alt": "User"}, true]} {tag: ["button", {"onclick": this.handleClick}, false]} {tag: ["div", {"data-sid": "header"}, false]} // $sid becomes data-id ``` ### 2. Component Instruction ```javascript {comp: [component_name, attributes, optional_innerhtml_function]} ``` **Format:** - `component_name`: String - Component to instantiate - `attributes`: Object - Props/attributes to pass - `optional_innerhtml_function`: Function - Contains slots and default content **CRITICAL**: When a component invocation contains slots or any child content, it MUST use the three-parameter form with a content function. Slots are NOT separate instructions after the component - they are contained within the component's content function. **Examples:** ```javascript // Simple component (no children) {comp: ["UserAvatar", {"data-userId": this.data.id}]} // Component with slots {comp: ["DataTable", {"data-rows": this.data.items}, (DataTable) => { const _output = []; _output.push({slot: ["header", {}, (header) => { const _output = []; _output.push({tag: ["th", {}, false]}); _output.push("Name"); _output.push(""); return [_output, this]; }]}); return [_output, this]; }]} ``` ### 3. Slot Instruction ```javascript {slot: [slot_name, attributes, render_function]} ``` **Format:** - `slot_name`: String - Slot identifier - `attributes`: Object - Props for slot - `render_function`: Function - Returns `[instructions, context]` **Example:** ```javascript {slot: ["actions", {}, (actions) => { const _output = []; _output.push({tag: ["button", {"onclick": this.delete}, false]}); _output.push("Delete"); _output.push(""); return [_output, this]; }]} ``` ### 4. String Output Raw strings are pushed directly: ```javascript _output.push({tag: ["div", {}, false]}); // Opening tag (instruction) _output.push("Hello, "); // Raw text _output.push(html(this.data.name)); // Escaped text _output.push("
"); // Closing tag (raw string!) ``` **CRITICAL**: Closing tags are ALWAYS raw strings, not instructions. ## Context Preservation ### v1 Pattern: `_that = this` The v1 pattern preserves component context through nested functions: ```javascript function render(Component, data, args, content) { let _that = this; // Preserve component instance let _output = []; _output.push({comp: ["Child", {}, function(Child) { let _output = []; // _that still refers to parent component _output.push(html(_that.data.value)); return [_output, _that]; }.bind(_that)]}); return [_output, _that]; } ``` ### v2 Pattern: Arrow Functions v2 uses arrow functions for natural context preservation: ```javascript function render(Component, data, args, content) { const _output = []; _output.push({comp: ["Child", {}, (Child) => { const _output = []; // 'this' refers to parent component naturally _output.push(html(this.data.value)); return [_output, this]; }]}); return [_output, this]; } ``` ## Runtime Processing ### Phase 1: Instruction Execution ```javascript let [instructions, context] = template.render.bind(component)( component, component.data, args, content_fn ); ``` ### Phase 2: Instruction Processing 1. **Build HTML**: Process all string and simple tag instructions 2. **Track Complex Elements**: Store elements with function attributes 3. **Single DOM Write**: `component.$.html(html.join(''))` 4. **Apply Attributes**: Attach event handlers and complex attributes 5. **Initialize Components**: Create child component instances ## Self-Closing HTML Tags The following HTML tags are automatically treated as self-closing: - `area`, `base`, `br`, `col`, `embed`, `hr`, `img`, `input` - `link`, `meta`, `param`, `source`, `track`, `wbr` ## Complete Example ### Template ```jqhtml
<#actions>
<% if (this.data.loading): %> <% else: %> <#header> ID Name Actions <#row> <%= row.id %> <%= row.name %> <% endif; %>
``` ### Compiled Output Structure ```javascript function render(Component, data, args, content) { const _output = []; _output.push({tag: ["div", {"class": "dashboard"}, false]}); // Header component with slots _output.push({comp: ["Header", {"data-title": "User Management"}, (Header) => { const _output = []; _output.push({slot: ["actions", {}, (actions) => { const _output = []; _output.push({tag: ["button", {"data-onclick": this.addUser}, false]}); _output.push("Add User"); _output.push(""); return [_output, this]; }]}); return [_output, this]; }]}); // ... rest of compilation _output.push("
"); return [_output, this]; } ``` ## Component CSS Classes When components are rendered, the runtime automatically applies CSS classes based on the component's inheritance hierarchy: ```html
``` This enables CSS targeting at any level of the hierarchy: ```css .Component { /* all components */ } .UserCard { /* specific component type */ } .BaseCard { /* all cards */ } ``` **Implementation Note**: The Component base class automatically applies these classes during initialization. This feature works regardless of minification since class names are preserved through the component's constructor name. ## Critical Implementation Rules ### What MUST Be Preserved 1. **Instruction Array Structure**: The `{tag:...}`, `{comp:...}`, `{slot:...}` format 2. **Return Tuple**: Functions MUST return `[instructions, context]` 3. **Deferred Component Init**: Components render as placeholders first 4. **Single DOM Write**: Build all HTML, write once 5. **Content Function**: Parent components receive content function to access slots ### Common Pitfalls 1. **Forgetting Return Format**: Must return `[array, context]`, not just array 2. **String vs Instruction**: Closing tags are strings, opening tags are instructions 3. **Context Loss**: Without proper binding, nested functions lose component reference 4. **Direct DOM Manipulation**: Never manipulate DOM during instruction building 5. **Missing html() Escaping**: User content must be escaped 6. **Slots Outside Components**: Slots must be inside component content functions