Reorganize RSpade directory structure for clarity
Improve Jqhtml_Integration.js documentation with hydration system explanation Add jqhtml-laravel integration packages for traditional Laravel projects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -54,7 +54,7 @@ Before RPC: 900+ Node.js process spawns during manifest build (~30-60s overhead)
|
||||
After RPC: Single Node.js process, reused across all sanitizations (~1-2s startup overhead)
|
||||
|
||||
### Parallel to JS Parser
|
||||
This architecture mirrors the JS parser RPC server pattern. See `/app/RSpade/Core/JavaScript/CLAUDE.md` for detailed RPC pattern documentation.
|
||||
This architecture mirrors the JS parser RPC server pattern. See `/app/RSpade/Core/JsParsers/CLAUDE.md` for detailed RPC pattern documentation.
|
||||
|
||||
## Js_CodeQuality_Rpc - RPC Server Architecture
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\RSpade\Commands\Rsx;
|
||||
|
||||
use App\Console\Commands\FrameworkDeveloperCommand;
|
||||
use App\RSpade\Core\JavaScript\Js_Transformer;
|
||||
use App\RSpade\Core\JsParsers\Js_Transformer;
|
||||
|
||||
class Rsx_Js_Transform_Command extends FrameworkDeveloperCommand
|
||||
{
|
||||
|
||||
@@ -2186,7 +2186,7 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
||||
|
||||
// Transform the file (will use cache if available)
|
||||
try {
|
||||
$transformed_code = \App\RSpade\Core\JavaScript\Js_Transformer::transform($file);
|
||||
$transformed_code = \App\RSpade\Core\JsParsers\Js_Transformer::transform($file);
|
||||
|
||||
// Write transformed code to a temp file
|
||||
$temp_file = storage_path('rsx-tmp/babel_' . md5($file) . '.js');
|
||||
|
||||
@@ -26,8 +26,8 @@ class Core_Bundle extends Rsx_Bundle_Abstract
|
||||
'app/RSpade/Core/Data',
|
||||
'app/RSpade/Core/Database',
|
||||
'app/RSpade/Core/SPA',
|
||||
'app/RSpade/Core/Debug', // Debug components (JS_Tree_Debug_*)
|
||||
'app/RSpade/Lib',
|
||||
'app/RSpade/Components',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\RSpade\Core\JavaScript;
|
||||
namespace App\RSpade\Core\JsParsers;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* use snake_case (underscore_wherever_possible).
|
||||
*/
|
||||
|
||||
namespace App\RSpade\Core\JavaScript;
|
||||
namespace App\RSpade\Core\JsParsers;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
use App\RSpade\Core\JavaScript\Js_Exception;
|
||||
use App\RSpade\Core\JsParsers\Js_Exception;
|
||||
|
||||
class Js_Parser
|
||||
{
|
||||
@@ -22,7 +22,7 @@ class Js_Parser
|
||||
/**
|
||||
* Node.js RPC server script path
|
||||
*/
|
||||
protected const RPC_SERVER_SCRIPT = 'app/RSpade/Core/JavaScript/resource/js-parser-server.js';
|
||||
protected const RPC_SERVER_SCRIPT = 'app/RSpade/Core/JsParsers/resource/js-parser-server.js';
|
||||
|
||||
/**
|
||||
* Unix socket path for RPC server
|
||||
@@ -10,7 +10,7 @@
|
||||
* use snake_case (underscore_wherever_possible).
|
||||
*/
|
||||
|
||||
namespace App\RSpade\Core\JavaScript;
|
||||
namespace App\RSpade\Core\JsParsers;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Symfony\Component\Process\Process;
|
||||
@@ -22,12 +22,12 @@ class Js_Transformer
|
||||
/**
|
||||
* Node.js transformer script path (RPC server)
|
||||
*/
|
||||
protected const RPC_SERVER_SCRIPT = 'app/RSpade/Core/JavaScript/resource/js-transformer-server.js';
|
||||
protected const RPC_SERVER_SCRIPT = 'app/RSpade/Core/JsParsers/resource/js-transformer-server.js';
|
||||
|
||||
/**
|
||||
* Transformer script path for availability checking (RPC server used for actual transformations)
|
||||
*/
|
||||
protected const TRANSFORMER_SCRIPT = 'app/RSpade/Core/JavaScript/resource/js-transformer.js';
|
||||
protected const TRANSFORMER_SCRIPT = 'app/RSpade/Core/JsParsers/resource/js-transformer.js';
|
||||
|
||||
/**
|
||||
* Cache directory for transformed JavaScript files
|
||||
@@ -99,7 +99,7 @@ When `start_rpc_server()` is called:
|
||||
```php
|
||||
$process = new Process([
|
||||
'node',
|
||||
base_path('app/RSpade/Core/JavaScript/resource/js-parser-server.js'),
|
||||
base_path('app/RSpade/Core/JsParsers/resource/js-parser-server.js'),
|
||||
'--socket=' . $socket_path
|
||||
]);
|
||||
$process->start();
|
||||
@@ -348,11 +348,11 @@ When implementing RPC server for another operation:
|
||||
|
||||
Reference these when implementing pattern elsewhere:
|
||||
|
||||
- **PHP Server Management:** `/system/app/RSpade/Core/JavaScript/Js_Parser.php`
|
||||
- **PHP Server Management:** `/system/app/RSpade/Core/JsParsers/Js_Parser.php`
|
||||
- Methods: `start_rpc_server()`, `stop_rpc_server()`, `ping_rpc_server()`
|
||||
- Lines: 528-680
|
||||
|
||||
- **Node.js RPC Server:** `/system/app/RSpade/Core/JavaScript/resource/js-parser-server.js`
|
||||
- **Node.js RPC Server:** `/system/app/RSpade/Core/JsParsers/resource/js-parser-server.js`
|
||||
- Full example server with line-delimited JSON handling
|
||||
- Socket cleanup, signal handlers, graceful shutdown
|
||||
|
||||
@@ -398,7 +398,7 @@ echo '{"id":1,"method":"ping"}' | nc -U storage/rsx-tmp/js-parser-server.sock
|
||||
### Manual Server Test
|
||||
```bash
|
||||
# Start server manually
|
||||
node system/app/RSpade/Core/JavaScript/resource/js-parser-server.js \
|
||||
node system/app/RSpade/Core/JsParsers/resource/js-parser-server.js \
|
||||
--socket=/tmp/test.sock
|
||||
|
||||
# In another terminal:
|
||||
@@ -51,7 +51,7 @@ The build process in Manifest.php follows these phases:
|
||||
|
||||
### Phase 1: File Discovery (__discover_files)
|
||||
- Scans directories from `config('rsx.manifest.scan_directories')`
|
||||
- Default: `['rsx', 'app/RSpade/Core/Js', 'app/RSpade/Modules']`
|
||||
- Default: `['rsx', 'app/RSpade/Core/Js', 'app/RSpade/Core/Manifest/Modules']`
|
||||
- Excludes: vendor/, node_modules/, .git/, storage/, public/
|
||||
- Returns array of file paths with basic stats (mtime, size)
|
||||
|
||||
@@ -213,7 +213,7 @@ This ensures **fail-fast behavior** - no silent failures.
|
||||
|
||||
### Built-in Modules
|
||||
|
||||
Located in `/app/RSpade/Modules/`:
|
||||
Located in `/app/RSpade/Core/Manifest/Modules/`:
|
||||
|
||||
- `Php_ManifestModule` - PHP reflection and attribute extraction
|
||||
- `Blade_ManifestModule` - Blade directive parsing
|
||||
@@ -310,7 +310,7 @@ When testing manifest functionality:
|
||||
- Cache file: `storage/rsx-build/manifest_data.php`
|
||||
- JS stubs: `storage/rsx-build/js-stubs/`
|
||||
- Model stubs: `storage/rsx-build/js-model-stubs/`
|
||||
- Default scan dirs: `['rsx', 'app/RSpade/Core/Js', 'app/RSpade/Modules']`
|
||||
- Default scan dirs: `['rsx', 'app/RSpade/Core/Js', 'app/RSpade/Core/Manifest/Modules']`
|
||||
|
||||
## Direct Data Access
|
||||
|
||||
|
||||
@@ -2494,7 +2494,7 @@ class Manifest
|
||||
|
||||
case 'js':
|
||||
console_debug('BUILD', "Parsing JS file: {$file_path}");
|
||||
$js_metadata = \App\RSpade\Core\JavaScript\Js_Parser::extract_metadata($absolute_path);
|
||||
$js_metadata = \App\RSpade\Core\JsParsers\Js_Parser::extract_metadata($absolute_path);
|
||||
$data = array_merge($data, $js_metadata);
|
||||
break;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\RSpade\Modules;
|
||||
namespace App\RSpade\Core\Manifest\Modules;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -1,14 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\RSpade\Services;
|
||||
namespace App\RSpade\Core\Task;
|
||||
|
||||
use App\RSpade\Core\Service\Rsx_Service_Abstract;
|
||||
use App\RSpade\Core\Task\Task_Instance;
|
||||
use App\RSpade\Core\Task\Task_Attribute;
|
||||
use App\RSpade\Core\Task\Schedule;
|
||||
use App\RSpade\Core\Task\Task_Status;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use App\RSpade\Core\Service\Rsx_Service_Abstract;
|
||||
use App\RSpade\Core\Task\Task_Instance;
|
||||
use App\RSpade\Core\Task\Task_Status;
|
||||
|
||||
/**
|
||||
* Cleanup Service
|
||||
@@ -5,7 +5,7 @@
|
||||
* use snake_case (underscore_wherever_possible).
|
||||
*/
|
||||
|
||||
namespace App\RSpade\Testing;
|
||||
namespace App\RSpade\Core\Testing;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
@@ -1,52 +1,104 @@
|
||||
/**
|
||||
* JQHTML Integration - Automatic component registration and binding
|
||||
* JQHTML Integration - Component Hydration System
|
||||
*
|
||||
* This module automatically:
|
||||
* 1. Registers component classes that extend Component
|
||||
* 2. Binds templates to component classes when names match
|
||||
* 3. Enables $(selector).component("Component_Name") syntax
|
||||
* This module bridges server-rendered HTML and client-side jqhtml components.
|
||||
*
|
||||
* == THE HYDRATION PROBLEM ==
|
||||
*
|
||||
* When PHP/Blade renders a page, jqhtml components appear as placeholder elements:
|
||||
*
|
||||
* Blade source: <User_Card $name="John" />
|
||||
* Rendered HTML: <div class="Component_Init"
|
||||
* data-component-init-name="User_Card"
|
||||
* data-component-args='{"name":"John"}'>
|
||||
* </div>
|
||||
*
|
||||
* These are just div tags - they have no behavior until JavaScript runs.
|
||||
* "Hydration" is the process of finding these placeholders and converting them
|
||||
* into live, interactive jqhtml components.
|
||||
*
|
||||
* == TWO-PHASE INITIALIZATION ==
|
||||
*
|
||||
* Phase 1: _on_framework_modules_define() - Component Registration
|
||||
* - Runs early in framework boot, before DOM is processed
|
||||
* - Registers all ES6 classes extending Component with jqhtml runtime
|
||||
* - After this phase, jqhtml knows: "User_Card" → UserCardClass
|
||||
*
|
||||
* Phase 2: _on_framework_modules_init() - DOM Hydration
|
||||
* - Runs after templates are loaded
|
||||
* - Finds all .Component_Init elements in the DOM
|
||||
* - Extracts component name and args from data attributes
|
||||
* - Calls $element.component(name, args) to hydrate each one
|
||||
* - Recursively processes nested components
|
||||
* - Triggers 'jqhtml_ready' when all components are initialized
|
||||
*
|
||||
* == THE TRANSFORMATION ==
|
||||
*
|
||||
* Before hydration:
|
||||
* <div class="Component_Init" data-component-init-name="User_Card" ...>
|
||||
*
|
||||
* After hydration:
|
||||
* <div class="User_Card"> ← Component_Init removed, component class added
|
||||
* [component template content] ← Template rendered into element
|
||||
* </div>
|
||||
*
|
||||
* The element is now a live component with event handlers, state, and lifecycle.
|
||||
*
|
||||
* == KEY PARTICIPANTS ==
|
||||
*
|
||||
* JqhtmlBladeCompiler.php - Transforms <Component /> tags into .Component_Init divs
|
||||
* jqhtml runtime - Maintains registry of component names → classes
|
||||
* This module - Orchestrates registration and hydration
|
||||
* $().component() - jQuery plugin that creates component instances
|
||||
*/
|
||||
class Jqhtml_Integration {
|
||||
/**
|
||||
* Compiled Jqhtml templates self-register. The developer (the framework in this case) is still
|
||||
* responsible for registering es6 component classes with jqhtml. This does so at an early stage
|
||||
* of framework init.
|
||||
* Phase 1: Register Component Classes
|
||||
*
|
||||
* Compiled .jqhtml templates self-register their render methods with jqhtml.
|
||||
* But the framework must separately register ES6 component classes (the ones
|
||||
* extending Component with lifecycle methods like on_create, on_load, etc).
|
||||
*
|
||||
* This runs during framework_modules_define, before any DOM processing.
|
||||
*/
|
||||
static _on_framework_modules_define() {
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Register Component Classes with jqhtml Runtime
|
||||
//
|
||||
// The Manifest knows all classes extending Component. We register each
|
||||
// with jqhtml so it can instantiate them by name during hydration.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
let jqhtml_components = Manifest.get_extending('Component');
|
||||
|
||||
console_debug('JQHTML_INIT', 'Registering ' + jqhtml_components.length + ' Components');
|
||||
|
||||
for (let component of jqhtml_components) {
|
||||
jqhtml.register_component(component.class_name, component.class_object);
|
||||
}
|
||||
|
||||
// Assign unique cache IDs to all static methods for component caching
|
||||
// This enables JQHTML to generate deterministic cache keys when functions
|
||||
// are passed as component arguments (e.g., DataGrid data_source functions)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Tag Static Methods with Cache IDs
|
||||
//
|
||||
// jqhtml caches component output based on args. When a function is passed
|
||||
// as an arg (e.g., DataGrid's data_source), we need a stable string key.
|
||||
//
|
||||
// Without this: data_source: function() {...} → no cache (functions aren't serializable)
|
||||
// With this: function._jqhtml_cache_id = "My_DataGrid.fetch_data" → cacheable
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
const all_classes = Manifest.get_all_classes();
|
||||
let methods_tagged = 0;
|
||||
|
||||
for (const class_info of all_classes) {
|
||||
const class_object = class_info.class_object;
|
||||
const class_name = class_info.class_name;
|
||||
|
||||
// Get all property names from the class object (static methods/properties)
|
||||
const property_names = Object.getOwnPropertyNames(class_object);
|
||||
|
||||
for (const property_name of property_names) {
|
||||
// Skip built-in properties and non-functions
|
||||
if (property_name === 'length' ||
|
||||
property_name === 'name' ||
|
||||
property_name === 'prototype') {
|
||||
if (property_name === 'length' || property_name === 'name' || property_name === 'prototype') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const property_value = class_object[property_name];
|
||||
|
||||
// Only tag functions (static methods)
|
||||
if (typeof property_value === 'function') {
|
||||
// Assign unique cache ID: "ClassName.methodName"
|
||||
property_value._jqhtml_cache_id = `${class_name}.${property_name}`;
|
||||
methods_tagged++;
|
||||
}
|
||||
@@ -55,24 +107,57 @@ class Jqhtml_Integration {
|
||||
|
||||
console_debug('JQHTML_INIT', `Tagged ${methods_tagged} static methods with _jqhtml_cache_id`);
|
||||
|
||||
// Set the cache key for jqhtml to the application key to enable component caching in local storage.
|
||||
// Modifying the application or the user logging out modifies the scope_key and will invalidate jqhtml cache.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Configure jqhtml Caching
|
||||
//
|
||||
// scope_key() changes when: app code changes, user logs out, site changes.
|
||||
// This automatically invalidates cached component renders.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
jqhtml.set_cache_key(Rsx.scope_key());
|
||||
|
||||
// set this to true if we desire jqhtml verbose output (probably not necessary for end developers)
|
||||
window.jqhtml.debug.verbose = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Framework modules init phase - Bind components and initialize DOM
|
||||
* This runs after templates are registered to bind component classes
|
||||
* @param {jQuery} [$scope] Optional scope to search within (defaults to body)
|
||||
* @returns {Array<Promise>|undefined} Array of promises for recursive calls, undefined for top-level
|
||||
* Phase 2: DOM Hydration
|
||||
*
|
||||
* Finds all .Component_Init placeholders and converts them into live components.
|
||||
*
|
||||
* == HYDRATION PROCESS ==
|
||||
*
|
||||
* For each .Component_Init element:
|
||||
*
|
||||
* 1. EXTRACT: Read data-component-init-name and data-component-args
|
||||
* 2. CLEANUP: Remove data attributes (prevents re-hydration, hides implementation)
|
||||
* 3. CAPTURE: Save innerHTML for slot/content() processing
|
||||
* 4. INSTANTIATE: Call $element.component(name, args)
|
||||
* 5. RECURSE: After render, hydrate any nested .Component_Init elements
|
||||
*
|
||||
* == NESTED COMPONENT HANDLING ==
|
||||
*
|
||||
* Components can contain other components in their Blade output:
|
||||
*
|
||||
* <User_Card> ← Parent hydrates first
|
||||
* <Avatar /> ← Child hydrates after parent renders
|
||||
* </User_Card>
|
||||
*
|
||||
* We skip nested .Component_Init elements on first pass (they're inside a parent
|
||||
* that hasn't rendered yet). After each component renders, we recursively scan
|
||||
* its content for children to hydrate.
|
||||
*
|
||||
* == ASYNC COMPLETION ==
|
||||
*
|
||||
* Hydration is async because components may have on_load() methods that fetch data.
|
||||
* We track all component promises and trigger 'jqhtml_ready' only when every
|
||||
* component (including nested ones) has completed its full lifecycle.
|
||||
*
|
||||
* @param {jQuery} [$scope] Scope to search (defaults to body). Recursive calls pass component.$
|
||||
* @returns {Array<Promise>|undefined} Promises for recursive calls; undefined for top-level
|
||||
*/
|
||||
static _on_framework_modules_init($scope) {
|
||||
const is_top_level = !$scope;
|
||||
const promises = [];
|
||||
const components_needing_init = ($scope || $('body')).find('.Component_Init');
|
||||
|
||||
if (components_needing_init.length > 0) {
|
||||
console_debug('JQHTML_INIT', `Initializing ${components_needing_init.length} DOM components`);
|
||||
}
|
||||
@@ -80,35 +165,27 @@ class Jqhtml_Integration {
|
||||
components_needing_init.each(function () {
|
||||
const $element = $(this);
|
||||
|
||||
// Skip if element is no longer attached to the document
|
||||
// (may have been removed by a parent component's .empty() call)
|
||||
// Guard: Element may have been removed by a parent component's render
|
||||
if (!document.contains($element[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any parent has Component_Init class - skip nested components
|
||||
// Guard: Skip nested components - they'll be processed after their parent renders
|
||||
let parent = $element[0].parentElement;
|
||||
while (parent) {
|
||||
if (parent.classList.contains('Component_Init')) {
|
||||
return; // Skip this element, it's nested
|
||||
return;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// STEP 1: Extract hydration data from placeholder element
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
const component_name = $element.attr('data-component-init-name');
|
||||
|
||||
// jQuery's .data() doesn't auto-parse JSON - we need to parse it manually
|
||||
let component_args = {};
|
||||
const args_string = $element.attr('data-component-args');
|
||||
|
||||
// Unset component- php side initialization args, it is no longer needed as a compionent attribute
|
||||
// Unsetting also prevents undesired access to this code in other parts of the program, prevening an
|
||||
// unwanted future dependency on this paradigm
|
||||
$element.removeAttr('data-component-init-name');
|
||||
$element.removeAttr('data-component-args');
|
||||
$element.removeData('component-init-name');
|
||||
$element.removeData('component-args');
|
||||
|
||||
let component_args = {};
|
||||
if (args_string) {
|
||||
try {
|
||||
component_args = JSON.parse(args_string);
|
||||
@@ -118,13 +195,22 @@ class Jqhtml_Integration {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// STEP 2: Remove hydration markers (cleanup)
|
||||
//
|
||||
// These attributes served their purpose. Removing them:
|
||||
// - Prevents accidental re-hydration
|
||||
// - Hides implementation details from DOM inspection
|
||||
// - Prevents other code from depending on this internal mechanism
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
$element.removeAttr('data-component-init-name');
|
||||
$element.removeAttr('data-component-args');
|
||||
$element.removeData('component-init-name');
|
||||
$element.removeData('component-args');
|
||||
|
||||
if (component_name) {
|
||||
// Transform $ prefixed keys to data- attributes
|
||||
let component_args_filtered = {};
|
||||
for (const [key, value] of Object.entries(component_args)) {
|
||||
// if (key.startsWith('$')) {
|
||||
// component_args_filtered[key.substring(1)] = value;
|
||||
// } else
|
||||
if (key.startsWith('data-')) {
|
||||
component_args_filtered[key.substring(5)] = value;
|
||||
} else {
|
||||
@@ -133,29 +219,41 @@ class Jqhtml_Integration {
|
||||
}
|
||||
|
||||
try {
|
||||
// Store inner HTML as string for nested component processing
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// STEP 3: Capture innerHTML for slot/content() processing
|
||||
//
|
||||
// Blade content between <Component>...</Component> tags
|
||||
// becomes available to the template via content() function
|
||||
// ─────────────────────────────────────────────────────────
|
||||
component_args_filtered._inner_html = $element.html();
|
||||
$element.empty();
|
||||
|
||||
// Remove the init class before instantiation to prevent re-initialization
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// STEP 4: Instantiate the component
|
||||
//
|
||||
// Remove .Component_Init first to prevent re-initialization
|
||||
// if this element is somehow scanned again
|
||||
// ─────────────────────────────────────────────────────────
|
||||
$element.removeClass('Component_Init');
|
||||
|
||||
// Create promise for this component's initialization
|
||||
const component_promise = new Promise((resolve) => {
|
||||
// Use jQuery component plugin to create the component
|
||||
// Plugin handles element internally, just pass args
|
||||
// Get the updated $element from
|
||||
// $().component(name, args) creates the component instance,
|
||||
// binds it to the element, and starts the lifecycle
|
||||
let component = $element.component(component_name, component_args_filtered).component();
|
||||
|
||||
// ─────────────────────────────────────────────────────
|
||||
// STEP 5: Recurse after render
|
||||
//
|
||||
// Component's template may contain nested .Component_Init
|
||||
// elements. We must wait for render before we can find them.
|
||||
//
|
||||
// Note: If the template changed the tag (e.g., tag="button"),
|
||||
// the original $element may have been replaced. Always use
|
||||
// component.$ to get the current element.
|
||||
// ─────────────────────────────────────────────────────
|
||||
component.on('render', function () {
|
||||
// Recursively collect promises from nested components
|
||||
|
||||
// Getting the updated component here - if the tag name was not div, the element would have been recreated, so we need to get the element set on the component, not from our earlier selector
|
||||
|
||||
const nested_promises = Jqhtml_Integration._on_framework_modules_init(component.$);
|
||||
promises.push(...nested_promises);
|
||||
|
||||
// Resolve this component's promise
|
||||
resolve();
|
||||
}).$;
|
||||
});
|
||||
@@ -168,7 +266,9 @@ class Jqhtml_Integration {
|
||||
}
|
||||
});
|
||||
|
||||
// Top-level call: spawn async handler to wait for all promises, then trigger event
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// COMPLETION: Top-level call waits for all components, then signals ready
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
if (is_top_level) {
|
||||
(async () => {
|
||||
await Promise.all(promises);
|
||||
@@ -177,26 +277,23 @@ class Jqhtml_Integration {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recursive call: return promises for parent to collect
|
||||
// Recursive calls return promises for parent to collect
|
||||
return promises;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered component names
|
||||
* @returns {Array<string>} Array of component names
|
||||
*/
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// Utility Methods (pass-through to jqhtml runtime)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/** Get all registered component names. Useful for debugging/introspection. */
|
||||
static get_component_names() {
|
||||
return jqhtml.get_component_names();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a component is registered
|
||||
* @param {string} name Component name
|
||||
* @returns {boolean} True if component is registered
|
||||
*/
|
||||
/** Check if a component is registered by name. */
|
||||
static has_component(name) {
|
||||
return jqhtml.has_component(name);
|
||||
}
|
||||
}
|
||||
|
||||
// RSX manifest automatically makes classes global - no manual assignment needed
|
||||
// Class is automatically made global by RSX manifest - no window assignment needed
|
||||
|
||||
0
app/RSpade/Vendor/.placeholder
vendored
0
app/RSpade/Vendor/.placeholder
vendored
Reference in New Issue
Block a user