Move small tasks from wishlist to todo, update npm packages Replace #[Auth] attributes with manual auth checks and code quality rule Remove on_jqhtml_ready lifecycle method from framework Complete ACL system with 100-based role indexing and /dev/acl tester WIP: ACL system implementation with debug instrumentation Convert rsx:check JS linting to RPC socket server Clean up docs and fix $id→$sid in man pages, remove SSR/FPC feature Reorganize wishlists: priority order, mark sublayouts complete, add email Update model_fetch docs: mark MVP complete, fix enum docs, reorganize Comprehensive documentation overhaul: clarity, compression, and critical rules Convert Contacts/Projects CRUD to Model.fetch() and add fetch_or_null() Add JS ORM relationship lazy-loading and fetch array handling Add JS ORM relationship fetching and CRUD documentation Fix ORM hydration and add IDE resolution for Base_* model stubs Rename Json_Tree_Component to JS_Tree_Debug_Component and move to framework Enhance JS ORM infrastructure and add Json_Tree class name badges 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
203 lines
8.6 KiB
JavaScript
Executable File
203 lines
8.6 KiB
JavaScript
Executable File
/**
|
|
* JQHTML Integration - Automatic component registration and binding
|
|
*
|
|
* 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
|
|
*/
|
|
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.
|
|
*/
|
|
static _on_framework_modules_define() {
|
|
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)
|
|
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') {
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
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
|
|
*/
|
|
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`);
|
|
}
|
|
|
|
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)
|
|
if (!document.contains($element[0])) {
|
|
return;
|
|
}
|
|
|
|
// Check if any parent has Component_Init class - skip nested components
|
|
let parent = $element[0].parentElement;
|
|
while (parent) {
|
|
if (parent.classList.contains('Component_Init')) {
|
|
return; // Skip this element, it's nested
|
|
}
|
|
parent = parent.parentElement;
|
|
}
|
|
|
|
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');
|
|
|
|
if (args_string) {
|
|
try {
|
|
component_args = JSON.parse(args_string);
|
|
} catch (e) {
|
|
console.error(`[JQHTML Integration] Failed to parse component args for ${component_name}:`, e);
|
|
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 {
|
|
component_args_filtered[key] = value;
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Store inner HTML as string for nested component processing
|
|
component_args_filtered._inner_html = $element.html();
|
|
$element.empty();
|
|
|
|
// Remove the init class before instantiation to prevent re-initialization
|
|
$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
|
|
let component = $element.component(component_name, component_args_filtered).component();
|
|
|
|
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();
|
|
}).$;
|
|
});
|
|
|
|
promises.push(component_promise);
|
|
} catch (error) {
|
|
console.error(`[JQHTML Integration] Failed to initialize component ${component_name}:`, error);
|
|
console.error('Error details:', error.stack || error);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Top-level call: spawn async handler to wait for all promises, then trigger event
|
|
if (is_top_level) {
|
|
(async () => {
|
|
await Promise.all(promises);
|
|
Rsx.trigger('jqhtml_ready');
|
|
})();
|
|
return;
|
|
}
|
|
|
|
// Recursive call: return promises for parent to collect
|
|
return promises;
|
|
}
|
|
|
|
/**
|
|
* Get all registered component names
|
|
* @returns {Array<string>} Array of component names
|
|
*/
|
|
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
|
|
*/
|
|
static has_component(name) {
|
|
return jqhtml.has_component(name);
|
|
}
|
|
}
|
|
|
|
// RSX manifest automatically makes classes global - no manual assignment needed
|