Rename after_load to on_loaded, add load_children option to load_detached_action, update npm
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1087,6 +1087,10 @@ class Spa {
|
|||||||
* These are merged with URL-extracted args (extra_args take precedence).
|
* These are merged with URL-extracted args (extra_args take precedence).
|
||||||
* Pass {use_cached_data: true} to have the action load with cached data
|
* Pass {use_cached_data: true} to have the action load with cached data
|
||||||
* without revalidation if cached data is available.
|
* without revalidation if cached data is available.
|
||||||
|
* @param {object} options - Optional behavior options
|
||||||
|
* @param {boolean} options.load_children - If true, renders the full component tree
|
||||||
|
* so all children (datagrids, views, etc.) also run their on_load(). Uses
|
||||||
|
* _load_render_only instead of _load_only. Useful for preloading/warming cache.
|
||||||
* @returns {Promise<Spa_Action|null>} The fully-loaded action instance, or null if route not found
|
* @returns {Promise<Spa_Action|null>} The fully-loaded action instance, or null if route not found
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
@@ -1101,8 +1105,13 @@ class Spa {
|
|||||||
* @example
|
* @example
|
||||||
* // With cached data (faster, no network request if cached)
|
* // With cached data (faster, no network request if cached)
|
||||||
* const action = await Spa.load_detached_action('/contacts/123', {use_cached_data: true});
|
* const action = await Spa.load_detached_action('/contacts/123', {use_cached_data: true});
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Preload with children (warms cache for entire page tree)
|
||||||
|
* const action = await Spa.load_detached_action('/contacts/123', {}, {load_children: true});
|
||||||
|
* action.stop();
|
||||||
*/
|
*/
|
||||||
static async load_detached_action(url, extra_args = {}) {
|
static async load_detached_action(url, extra_args = {}, options = {}) {
|
||||||
// Parse URL and match to route
|
// Parse URL and match to route
|
||||||
const parsed = Spa.parse_url(url);
|
const parsed = Spa.parse_url(url);
|
||||||
const url_without_hash = parsed.path + parsed.search;
|
const url_without_hash = parsed.path + parsed.search;
|
||||||
@@ -1116,12 +1125,14 @@ class Spa {
|
|||||||
const action_class = route_match.action_class;
|
const action_class = route_match.action_class;
|
||||||
const action_name = action_class.name;
|
const action_name = action_class.name;
|
||||||
|
|
||||||
// Merge URL args with extra_args (extra_args take precedence)
|
// _load_only: action on_load() only, no children
|
||||||
// Include _load_only to skip render/on_ready (detached, no DOM needed)
|
// _load_render_only: renders full tree, all children run on_load() too
|
||||||
|
const lifecycle_flag = options.load_children ? '_load_render_only' : '_load_only';
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
...route_match.args,
|
...route_match.args,
|
||||||
...extra_args,
|
...extra_args,
|
||||||
_load_only: true
|
[lifecycle_flag]: true
|
||||||
};
|
};
|
||||||
|
|
||||||
console_debug('Spa', `load_detached_action: Loading ${action_name} with args:`, args);
|
console_debug('Spa', `load_detached_action: Loading ${action_name} with args:`, args);
|
||||||
|
|||||||
844
app/RSpade/man/custom_playwright_tests_spa.txt
Executable file
844
app/RSpade/man/custom_playwright_tests_spa.txt
Executable file
@@ -0,0 +1,844 @@
|
|||||||
|
PLAYWRIGHT_SPA(7) RSX Framework PLAYWRIGHT_SPA(7)
|
||||||
|
|
||||||
|
NAME
|
||||||
|
custom_playwright_tests_spa - writing custom Playwright tests for
|
||||||
|
SPA applications with jqhtml component lifecycle awareness
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
node bin/my-test.js [auth-token]
|
||||||
|
|
||||||
|
Where auth-token is generated by the PHP helper:
|
||||||
|
|
||||||
|
php -r "
|
||||||
|
foreach (file('.env') as \$line) {
|
||||||
|
if (strpos(\$line, 'APP_KEY=') === 0) {
|
||||||
|
\$app_key = trim(substr(\$line, 8));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\$payload = json_encode([
|
||||||
|
'url' => '/initial-route',
|
||||||
|
'user_id' => 1,
|
||||||
|
'portal' => false
|
||||||
|
]);
|
||||||
|
echo hash_hmac('sha256', \$payload, \$app_key);
|
||||||
|
"
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
rsx:debug is designed for single-page-load diagnostics. For tests
|
||||||
|
that require multi-step SPA navigation (e.g., verifying cache
|
||||||
|
behavior across page transitions, testing component state
|
||||||
|
persistence), you need a custom Playwright script.
|
||||||
|
|
||||||
|
This document covers the patterns required to write reliable
|
||||||
|
Playwright tests against an RSX SPA application, including
|
||||||
|
authentication, SPA navigation, and observing jqhtml component
|
||||||
|
lifecycle events like loading spinners and data rendering.
|
||||||
|
|
||||||
|
AUTHENTICATION
|
||||||
|
RSX uses backdoor header-based auth in development. The same
|
||||||
|
mechanism that powers rsx:debug works for custom scripts.
|
||||||
|
|
||||||
|
Required Headers
|
||||||
|
X-Dev-Auth-User-Id User ID to authenticate as
|
||||||
|
X-Dev-Auth-Token HMAC-SHA256 signature (see SYNOPSIS)
|
||||||
|
X-Playwright-Test Set to '1' for plain-text error responses
|
||||||
|
|
||||||
|
CRITICAL: Auth Headers Only on First Document Request
|
||||||
|
Auth headers must ONLY be sent on the initial document request,
|
||||||
|
NOT on every request. Sending auth headers on Ajax/XHR calls
|
||||||
|
causes RsxAuth::login() to run repeatedly, which regenerates the
|
||||||
|
session on each request. This makes Rsx.validate_session() detect
|
||||||
|
a session_hash mismatch and trigger a full page reload via
|
||||||
|
location.replace(), breaking SPA navigation entirely.
|
||||||
|
|
||||||
|
After the initial document request sets a session cookie, all
|
||||||
|
subsequent requests authenticate via that cookie automatically.
|
||||||
|
|
||||||
|
let initial_doc_sent = false;
|
||||||
|
|
||||||
|
await page.route('**/*', async (route, request) => {
|
||||||
|
const url = request.url();
|
||||||
|
if (url.startsWith(BASE_URL)) {
|
||||||
|
const headers = { ...request.headers() };
|
||||||
|
if (!initial_doc_sent
|
||||||
|
&& request.resourceType() === 'document') {
|
||||||
|
Object.assign(headers, authHeaders);
|
||||||
|
initial_doc_sent = true;
|
||||||
|
} else {
|
||||||
|
headers['X-Playwright-Test'] = '1';
|
||||||
|
}
|
||||||
|
await route.continue({ headers });
|
||||||
|
} else {
|
||||||
|
await route.continue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
The X-Playwright-Test header is safe to send on every request
|
||||||
|
(it only affects error response formatting). The auth headers
|
||||||
|
(X-Dev-Auth-User-Id, X-Dev-Auth-Token) must be restricted.
|
||||||
|
|
||||||
|
Token Scope
|
||||||
|
The HMAC token is signed against a specific URL. For SPA tests,
|
||||||
|
only the initial page.goto() URL matters -- all subsequent SPA
|
||||||
|
navigations happen client-side and do not trigger new page loads.
|
||||||
|
Generate the token for the first URL you navigate to.
|
||||||
|
|
||||||
|
SESSION VALIDATION
|
||||||
|
After initial page load, RSX runs Rsx.validate_session() on
|
||||||
|
every SPA dispatch. This Ajax call compares the server's current
|
||||||
|
session_hash with the hash stored in window.rsxapp.session_hash.
|
||||||
|
If they differ, RSX does location.replace() to force a full
|
||||||
|
page reload.
|
||||||
|
|
||||||
|
In test environments using dev auth headers, the session
|
||||||
|
established by the header-based login may have a different hash
|
||||||
|
than what validate_session returns. This is a test environment
|
||||||
|
artifact -- in production, sessions are stable.
|
||||||
|
|
||||||
|
Patching validate_session
|
||||||
|
After the initial page loads and the SPA is ready, patch
|
||||||
|
validate_session to prevent false-positive reloads:
|
||||||
|
|
||||||
|
// After initial page.goto() and wait_for_rsx_ready()
|
||||||
|
await page.evaluate(() => {
|
||||||
|
Rsx.validate_session = async () => true;
|
||||||
|
});
|
||||||
|
|
||||||
|
This must happen AFTER the initial page is fully loaded (after
|
||||||
|
_debug_ready fires) but BEFORE any SPA dispatch calls.
|
||||||
|
|
||||||
|
Detecting Full Page Reloads
|
||||||
|
Track document requests after the initial load to catch
|
||||||
|
unexpected reloads caused by validate_session or other issues:
|
||||||
|
|
||||||
|
let page_reload_count = 0;
|
||||||
|
page.on('request', req => {
|
||||||
|
if (req.resourceType() === 'document'
|
||||||
|
&& initial_doc_sent) {
|
||||||
|
page_reload_count++;
|
||||||
|
console.log(`!! UNEXPECTED RELOAD: ${req.url()}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
SPA NAVIGATION
|
||||||
|
RSX SPA applications use client-side routing via Spa.dispatch().
|
||||||
|
Understanding the difference between browser navigation and SPA
|
||||||
|
navigation is critical for writing correct tests.
|
||||||
|
|
||||||
|
Browser Navigation (page.goto)
|
||||||
|
Use ONLY for the initial page load:
|
||||||
|
|
||||||
|
await page.goto(`${BASE_URL}/contacts`, {
|
||||||
|
waitUntil: 'commit'
|
||||||
|
});
|
||||||
|
|
||||||
|
Use 'commit' rather than 'networkidle' -- the SPA will fire many
|
||||||
|
async requests during component lifecycle. Use RSX lifecycle
|
||||||
|
events (see LIFECYCLE EVENTS below) for precise ready detection.
|
||||||
|
|
||||||
|
SPA Navigation (Spa.dispatch)
|
||||||
|
For all subsequent navigation, use Spa.dispatch() directly:
|
||||||
|
|
||||||
|
await page.evaluate((path) => {
|
||||||
|
Spa.dispatch(path);
|
||||||
|
}, '/engagements');
|
||||||
|
|
||||||
|
Do NOT click sidebar links with page.evaluate + element.click().
|
||||||
|
The jQuery .click() override that prevents default and triggers
|
||||||
|
SPA dispatch may not fire correctly from Playwright's evaluation
|
||||||
|
context. Calling Spa.dispatch() directly is deterministic.
|
||||||
|
|
||||||
|
Context Destruction
|
||||||
|
SPA navigation replaces the DOM content area. Playwright's
|
||||||
|
execution context can be destroyed during this process. Any
|
||||||
|
page.evaluate() call during a SPA transition may throw:
|
||||||
|
|
||||||
|
Error: Execution context was destroyed
|
||||||
|
|
||||||
|
This is expected. Wrap evaluate calls in try/catch if they run
|
||||||
|
during or immediately after SPA navigation. The addInitScript
|
||||||
|
approach (see below) survives these transitions.
|
||||||
|
|
||||||
|
LIFECYCLE EVENTS
|
||||||
|
RSX provides two key events for determining when the application
|
||||||
|
is ready. These replace arbitrary timeouts with precise signals.
|
||||||
|
|
||||||
|
_debug_ready (Initial Page Load)
|
||||||
|
Fires once after ALL framework init phases complete, including
|
||||||
|
SPA action dispatch. This is the definitive "page is fully
|
||||||
|
loaded" signal for the initial page.goto():
|
||||||
|
|
||||||
|
async function wait_for_rsx_ready() {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (window.Rsx && window.Rsx.on) {
|
||||||
|
Rsx.on('_debug_ready', () => resolve());
|
||||||
|
} else {
|
||||||
|
// Fallback for non-RSX pages
|
||||||
|
document.addEventListener(
|
||||||
|
'DOMContentLoaded',
|
||||||
|
() => setTimeout(resolve, 200)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
The _debug_ready event fires after these phases complete:
|
||||||
|
1. framework_init (framework bootstrap)
|
||||||
|
2. modules_init (manifest, SPA router setup)
|
||||||
|
3. app_init (application module init)
|
||||||
|
4. _spa_init (Spa.dispatch() -- awaits full action lifecycle)
|
||||||
|
5. app_ready (final ready hooks)
|
||||||
|
|
||||||
|
Because _spa_init awaits the SPA action's full lifecycle
|
||||||
|
(including on_load data fetching and on_ready), _debug_ready
|
||||||
|
guarantees the first action is fully rendered with data.
|
||||||
|
|
||||||
|
spa_dispatch_ready (SPA Navigation)
|
||||||
|
Fires after each SPA dispatch completes (action's on_ready
|
||||||
|
has fired and all children are ready):
|
||||||
|
|
||||||
|
async function spa_navigate(path) {
|
||||||
|
// Set up ready listener BEFORE dispatching
|
||||||
|
const ready = page.evaluate(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
Rsx.on('spa_dispatch_ready', () => resolve());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dispatch navigation
|
||||||
|
try {
|
||||||
|
await page.evaluate((p) => {
|
||||||
|
Spa.dispatch(p);
|
||||||
|
}, path);
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
// Wait for action to fully load
|
||||||
|
await ready;
|
||||||
|
|
||||||
|
// Optional: wait for dynamic height recalculation
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
IMPORTANT: Register the spa_dispatch_ready listener BEFORE
|
||||||
|
calling Spa.dispatch(). The promise must be waiting before
|
||||||
|
the event fires, or it will be missed.
|
||||||
|
|
||||||
|
Dynamic Height Settle Time
|
||||||
|
DataGrids with $rows="dynamic" recalculate per_page from
|
||||||
|
viewport height with a 200ms debounce after render. Add a
|
||||||
|
500ms wait after the ready event to allow this recalculation
|
||||||
|
to complete:
|
||||||
|
|
||||||
|
const DYNAMIC_HEIGHT_SETTLE = 500;
|
||||||
|
await ready_promise;
|
||||||
|
await page.waitForTimeout(DYNAMIC_HEIGHT_SETTLE);
|
||||||
|
|
||||||
|
OBSERVING STATE
|
||||||
|
jqhtml components have async lifecycles: on_create (sync) ->
|
||||||
|
render (sync) -> on_load (async) -> on_ready (async). A DataGrid
|
||||||
|
may show a loading spinner for 50-500ms before data appears.
|
||||||
|
Point-in-time checks miss these transitions.
|
||||||
|
|
||||||
|
The Wrong Way: Point-in-Time Checks
|
||||||
|
This misses fast transitions:
|
||||||
|
|
||||||
|
// BAD: spinner may appear and disappear between these checks
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
const visible = await page.evaluate(() => {
|
||||||
|
return !!document.querySelector('.spinner.is-visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
The Right Way: addInitScript with Polling Observer
|
||||||
|
Inject a persistent polling loop via addInitScript. This runs on
|
||||||
|
every page load (including the initial one), survives SPA
|
||||||
|
navigations that destroy the execution context, and captures
|
||||||
|
every state transition:
|
||||||
|
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
let _last_state = null;
|
||||||
|
let _step = 'init';
|
||||||
|
let _step_start = Date.now();
|
||||||
|
|
||||||
|
// Allow the test to label steps
|
||||||
|
window.__test_set_step = function(step) {
|
||||||
|
_step = step;
|
||||||
|
_step_start = Date.now();
|
||||||
|
_last_state = null;
|
||||||
|
console.log(`[TEST] === ${step} === (t=0)`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Poll every 10ms, log transitions via console
|
||||||
|
setInterval(() => {
|
||||||
|
const spinner = !!document.querySelector(
|
||||||
|
'.datagrid-loading-overlay.is-visible'
|
||||||
|
);
|
||||||
|
const rows = document.querySelectorAll(
|
||||||
|
'.DataGrid_Table tr:not(.placeholder-row)' +
|
||||||
|
':not(.empty-row):not(.loading-row)'
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const key = `s=${spinner},r=${rows}`;
|
||||||
|
if (key !== _last_state) {
|
||||||
|
const elapsed = Date.now() - _step_start;
|
||||||
|
const label = spinner ? 'SPINNER'
|
||||||
|
: (rows > 0 ? `DATA(${rows})` : 'EMPTY');
|
||||||
|
console.log(`[TEST] ${elapsed}ms: ${label}`);
|
||||||
|
_last_state = key;
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
Capture these logs from the Playwright side:
|
||||||
|
|
||||||
|
const state_log = [];
|
||||||
|
page.on('console', msg => {
|
||||||
|
const text = msg.text();
|
||||||
|
if (text.startsWith('[TEST]')) {
|
||||||
|
state_log.push(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Setting Steps
|
||||||
|
Label each phase of your test so the log is readable:
|
||||||
|
|
||||||
|
async function set_step(label) {
|
||||||
|
try {
|
||||||
|
await page.evaluate((l) => {
|
||||||
|
if (window.__test_set_step) {
|
||||||
|
window.__test_set_step(l);
|
||||||
|
}
|
||||||
|
}, label);
|
||||||
|
} catch(e) {
|
||||||
|
// Context may be destroyed during SPA nav
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Interpreting Output
|
||||||
|
A healthy cached SPA navigation looks like:
|
||||||
|
|
||||||
|
[TEST] === STEP 3 === (t=0)
|
||||||
|
[TEST] 30ms: DATA(17) <-- instant from cache
|
||||||
|
|
||||||
|
A broken/uncached navigation looks like:
|
||||||
|
|
||||||
|
[TEST] === STEP 3 === (t=0)
|
||||||
|
[TEST] 30ms: EMPTY
|
||||||
|
[TEST] 150ms: SPINNER
|
||||||
|
[TEST] 800ms: DATA(10) <-- had to fetch from server
|
||||||
|
[TEST] 1200ms: DATA(17) <-- dynamic resize reload
|
||||||
|
|
||||||
|
TRACKING XHR CALLS
|
||||||
|
Monitor Ajax requests to understand how many server round-trips
|
||||||
|
a navigation triggers:
|
||||||
|
|
||||||
|
let xhr_count = 0;
|
||||||
|
page.on('request', req => {
|
||||||
|
if (req.url().includes('datagrid_fetch')) xhr_count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset before each step
|
||||||
|
xhr_count = 0;
|
||||||
|
await spa_navigate('/contacts');
|
||||||
|
console.log(`XHR calls: ${xhr_count}`);
|
||||||
|
|
||||||
|
This is essential for diagnosing redundant loads. A well-cached
|
||||||
|
SPA return navigation should trigger 1 XHR (background
|
||||||
|
revalidation) not 2-4.
|
||||||
|
|
||||||
|
VIEWPORT AND DYNAMIC SIZING
|
||||||
|
DataGrids in dynamic row mode calculate per_page from viewport
|
||||||
|
height. Inconsistent viewport sizes between test runs produce
|
||||||
|
different per_page values, which produce different cache keys,
|
||||||
|
which break cache hit expectations.
|
||||||
|
|
||||||
|
Always set an explicit viewport:
|
||||||
|
|
||||||
|
const context = await browser.newContext({
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
viewport: { width: 1920, height: 1080 }
|
||||||
|
});
|
||||||
|
|
||||||
|
FULL EXAMPLE
|
||||||
|
A complete, runnable SPA navigation test. Copy and customize
|
||||||
|
the route paths, observer selectors, and step sequence for
|
||||||
|
your specific test scenario.
|
||||||
|
|
||||||
|
See also: bin/test-datagrid-cache.js (working reference)
|
||||||
|
|
||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* SPA Navigation Test Template
|
||||||
|
*
|
||||||
|
* Tests multi-step SPA navigation with component state
|
||||||
|
* observation. Customize ROUTE_A, ROUTE_B, and the
|
||||||
|
* addInitScript observer for your specific scenario.
|
||||||
|
*
|
||||||
|
* AUTH: Headers only on first document request.
|
||||||
|
* WAIT: RSX lifecycle events (_debug_ready,
|
||||||
|
* spa_dispatch_ready) instead of arbitrary timeouts.
|
||||||
|
*/
|
||||||
|
const { chromium } = require(
|
||||||
|
'/var/www/html/system/node_modules/playwright'
|
||||||
|
);
|
||||||
|
|
||||||
|
const BASE_URL = 'https://myapp.dev.example.com';
|
||||||
|
const USER_ID = '1';
|
||||||
|
const TOKEN = process.argv[2] || null;
|
||||||
|
const ROUTE_A = '/contacts'; // Primary route to test
|
||||||
|
const ROUTE_B = '/engagements'; // Route to navigate away to
|
||||||
|
const DYNAMIC_HEIGHT_SETTLE = 500;
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const browser = await chromium.launch({
|
||||||
|
headless: true,
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
});
|
||||||
|
const context = await browser.newContext({
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
viewport: { width: 1920, height: 1080 }
|
||||||
|
});
|
||||||
|
const page = await context.newPage();
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// AUTH: headers only on first document request
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Sending auth headers on every request causes
|
||||||
|
// RsxAuth::login() to regenerate the session,
|
||||||
|
// breaking validate_session(). After the first
|
||||||
|
// document response sets a cookie, all subsequent
|
||||||
|
// requests authenticate via that cookie.
|
||||||
|
const authHeaders = {
|
||||||
|
'X-Dev-Auth-User-Id': USER_ID,
|
||||||
|
'X-Playwright-Test': '1'
|
||||||
|
};
|
||||||
|
if (TOKEN) {
|
||||||
|
authHeaders['X-Dev-Auth-Token'] = TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
let initial_doc_sent = false;
|
||||||
|
await page.route('**/*', async (route, request) => {
|
||||||
|
const url = request.url();
|
||||||
|
if (url.startsWith(BASE_URL)) {
|
||||||
|
const headers = { ...request.headers() };
|
||||||
|
if (!initial_doc_sent
|
||||||
|
&& request.resourceType() === 'document'
|
||||||
|
) {
|
||||||
|
Object.assign(headers, authHeaders);
|
||||||
|
initial_doc_sent = true;
|
||||||
|
} else {
|
||||||
|
headers['X-Playwright-Test'] = '1';
|
||||||
|
}
|
||||||
|
await route.continue({ headers });
|
||||||
|
} else {
|
||||||
|
await route.continue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Console log capture
|
||||||
|
// -------------------------------------------------
|
||||||
|
const state_log = [];
|
||||||
|
page.on('console', msg => {
|
||||||
|
const text = msg.text();
|
||||||
|
if (text.startsWith('[TEST]')) {
|
||||||
|
state_log.push(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Persistent observer via addInitScript
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Survives SPA navigations. Polls every 10ms and
|
||||||
|
// logs state transitions via console. Customize the
|
||||||
|
// selectors for your component under test.
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
let _last_state = null;
|
||||||
|
let _step_start = Date.now();
|
||||||
|
|
||||||
|
window.__test_set_step = function(step) {
|
||||||
|
_step_start = Date.now();
|
||||||
|
_last_state = null;
|
||||||
|
console.log(
|
||||||
|
`[TEST] === ${step} === (t=0)`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
// -- Customize these selectors --
|
||||||
|
const spinner = !!document.querySelector(
|
||||||
|
'.datagrid-loading-overlay.is-visible'
|
||||||
|
);
|
||||||
|
const tbody = document.querySelector(
|
||||||
|
'.DataGrid_Table'
|
||||||
|
);
|
||||||
|
let data_rows = 0;
|
||||||
|
if (tbody) {
|
||||||
|
data_rows = tbody.querySelectorAll(
|
||||||
|
'tr:not(.placeholder-row)' +
|
||||||
|
':not(.empty-row)' +
|
||||||
|
':not(.loading-row)'
|
||||||
|
).length;
|
||||||
|
}
|
||||||
|
// -- End customizable selectors --
|
||||||
|
|
||||||
|
const key = `s=${spinner},r=${data_rows}`;
|
||||||
|
if (key !== _last_state) {
|
||||||
|
const ms = Date.now() - _step_start;
|
||||||
|
const label = spinner
|
||||||
|
? 'SPINNER'
|
||||||
|
: (data_rows > 0
|
||||||
|
? `DATA(${data_rows})`
|
||||||
|
: 'EMPTY');
|
||||||
|
console.log(
|
||||||
|
`[TEST] ${String(ms).padStart(5)}`
|
||||||
|
+ `ms: ${label}`
|
||||||
|
);
|
||||||
|
_last_state = key;
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// XHR tracking
|
||||||
|
// -------------------------------------------------
|
||||||
|
let xhr_count = 0;
|
||||||
|
page.on('request', req => {
|
||||||
|
if (req.url().includes('datagrid_fetch')) {
|
||||||
|
xhr_count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Full page reload detection
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Any document request after initial load means
|
||||||
|
// something broke SPA navigation (likely
|
||||||
|
// validate_session or auth header issue).
|
||||||
|
let reload_count = 0;
|
||||||
|
page.on('request', req => {
|
||||||
|
if (req.resourceType() === 'document'
|
||||||
|
&& initial_doc_sent
|
||||||
|
) {
|
||||||
|
reload_count++;
|
||||||
|
console.log(
|
||||||
|
` !! UNEXPECTED RELOAD: ${req.url()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// -------------------------------------------------
|
||||||
|
|
||||||
|
// Label test steps for the observer log
|
||||||
|
async function set_step(label) {
|
||||||
|
try {
|
||||||
|
await page.evaluate((l) => {
|
||||||
|
if (window.__test_set_step) {
|
||||||
|
window.__test_set_step(l);
|
||||||
|
}
|
||||||
|
}, label);
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print and reset the observer log
|
||||||
|
function flush_log() {
|
||||||
|
console.log(state_log.join('\n'));
|
||||||
|
state_log.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for RSX _debug_ready (initial page load)
|
||||||
|
async function wait_for_rsx_ready() {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (window.Rsx && window.Rsx.on) {
|
||||||
|
Rsx.on('_debug_ready',
|
||||||
|
() => resolve());
|
||||||
|
} else {
|
||||||
|
if (document.readyState
|
||||||
|
=== 'complete'
|
||||||
|
|| document.readyState
|
||||||
|
=== 'interactive'
|
||||||
|
) {
|
||||||
|
setTimeout(resolve, 200);
|
||||||
|
} else {
|
||||||
|
document.addEventListener(
|
||||||
|
'DOMContentLoaded',
|
||||||
|
() => setTimeout(
|
||||||
|
resolve, 200
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPA navigate and wait for action ready
|
||||||
|
async function spa_navigate(path) {
|
||||||
|
xhr_count = 0;
|
||||||
|
|
||||||
|
// Listen BEFORE dispatching
|
||||||
|
const ready = page.evaluate(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
Rsx.on('spa_dispatch_ready',
|
||||||
|
() => resolve());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.evaluate((p) => {
|
||||||
|
Spa.dispatch(p);
|
||||||
|
}, path);
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
await ready;
|
||||||
|
await page.waitForTimeout(
|
||||||
|
DYNAMIC_HEIGHT_SETTLE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Test steps
|
||||||
|
// -------------------------------------------------
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('=== SPA Navigation Test ===');
|
||||||
|
console.log(`Viewport: 1920x1080`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// STEP 1: Cold start — first visit to ROUTE_A
|
||||||
|
console.log(
|
||||||
|
`--- STEP 1: First visit to ${ROUTE_A}`
|
||||||
|
+ ` (cold start) ---`
|
||||||
|
);
|
||||||
|
state_log.length = 0;
|
||||||
|
xhr_count = 0;
|
||||||
|
await set_step('STEP 1');
|
||||||
|
await page.goto(`${BASE_URL}${ROUTE_A}`, {
|
||||||
|
waitUntil: 'commit'
|
||||||
|
});
|
||||||
|
await wait_for_rsx_ready();
|
||||||
|
await page.waitForTimeout(DYNAMIC_HEIGHT_SETTLE);
|
||||||
|
await page.waitForLoadState('networkidle')
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
// Patch validate_session after initial load.
|
||||||
|
// Dev auth sessions have a different hash than
|
||||||
|
// what validate_session returns — test artifact.
|
||||||
|
await page.evaluate(() => {
|
||||||
|
Rsx.validate_session = async () => true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const s1_xhrs = xhr_count;
|
||||||
|
flush_log();
|
||||||
|
console.log(` XHR count: ${s1_xhrs}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// STEP 2: Navigate away to ROUTE_B
|
||||||
|
console.log(
|
||||||
|
`--- STEP 2: SPA navigate to`
|
||||||
|
+ ` ${ROUTE_B} ---`
|
||||||
|
);
|
||||||
|
await set_step('STEP 2');
|
||||||
|
await spa_navigate(ROUTE_B);
|
||||||
|
flush_log();
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// STEP 3: Return to ROUTE_A (2nd visit)
|
||||||
|
console.log(
|
||||||
|
`--- STEP 3: SPA back to ${ROUTE_A}`
|
||||||
|
+ ` (2nd visit) ---`
|
||||||
|
);
|
||||||
|
xhr_count = 0;
|
||||||
|
await set_step('STEP 3');
|
||||||
|
await spa_navigate(ROUTE_A);
|
||||||
|
const s3_xhrs = xhr_count;
|
||||||
|
flush_log();
|
||||||
|
console.log(` XHR count: ${s3_xhrs}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// STEP 4: Navigate away again
|
||||||
|
console.log(
|
||||||
|
`--- STEP 4: SPA navigate to`
|
||||||
|
+ ` ${ROUTE_B} ---`
|
||||||
|
);
|
||||||
|
await set_step('STEP 4');
|
||||||
|
await spa_navigate(ROUTE_B);
|
||||||
|
flush_log();
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// STEP 5: Return to ROUTE_A (3rd visit)
|
||||||
|
console.log(
|
||||||
|
`--- STEP 5: SPA back to ${ROUTE_A}`
|
||||||
|
+ ` (3rd visit) ---`
|
||||||
|
);
|
||||||
|
xhr_count = 0;
|
||||||
|
await set_step('STEP 5');
|
||||||
|
await spa_navigate(ROUTE_A);
|
||||||
|
const s5_xhrs = xhr_count;
|
||||||
|
flush_log();
|
||||||
|
console.log(` XHR count: ${s5_xhrs}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Summary
|
||||||
|
// -------------------------------------------------
|
||||||
|
console.log('=== SUMMARY ===');
|
||||||
|
console.log(
|
||||||
|
`Step 1 (cold): ${s1_xhrs} XHRs`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Step 3 (2nd visit): ${s3_xhrs} XHRs`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Step 5 (3rd visit): ${s5_xhrs} XHRs`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Page reloads: ${reload_count}`
|
||||||
|
+ ` (should be 0)`
|
||||||
|
);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
if (reload_count > 0) {
|
||||||
|
console.log(
|
||||||
|
'FAIL: Full page reloads detected'
|
||||||
|
+ ' during SPA navigation.'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
'OK: No full page reloads'
|
||||||
|
+ ' — true SPA navigation achieved.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch(err => {
|
||||||
|
console.error('Test failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
COMMON PITFALLS
|
||||||
|
|
||||||
|
Auth Headers on Every Request (Session Regeneration)
|
||||||
|
The most critical pitfall. Sending X-Dev-Auth-User-Id on every
|
||||||
|
request (not just the first document request) causes
|
||||||
|
RsxAuth::login() to regenerate the session on each hit. This
|
||||||
|
changes the session_hash, which Rsx.validate_session() detects
|
||||||
|
as a mismatch, triggering location.replace() -- a full page
|
||||||
|
reload that destroys SPA state.
|
||||||
|
|
||||||
|
Symptom: "Execution context was destroyed" errors, window
|
||||||
|
markers disappearing, document request count incrementing
|
||||||
|
after SPA dispatch calls.
|
||||||
|
|
||||||
|
Fix: Auth headers on first document request only. All
|
||||||
|
subsequent requests use the session cookie set by that first
|
||||||
|
response. See AUTHENTICATION section.
|
||||||
|
|
||||||
|
validate_session Causing Full Page Reloads
|
||||||
|
Even with correct auth header scoping, dev-auth-established
|
||||||
|
sessions may have a hash mismatch with what validate_session
|
||||||
|
returns. This is a test environment artifact.
|
||||||
|
|
||||||
|
Symptom: SPA dispatch triggers a full page reload despite
|
||||||
|
auth headers being correctly scoped.
|
||||||
|
|
||||||
|
Fix: Patch Rsx.validate_session after initial load. See
|
||||||
|
SESSION VALIDATION section.
|
||||||
|
|
||||||
|
Playwright Require Path
|
||||||
|
Playwright is installed in system/node_modules/, not in the
|
||||||
|
project root. If your script is outside system/, use an absolute
|
||||||
|
require path:
|
||||||
|
|
||||||
|
require('/var/www/html/system/node_modules/playwright')
|
||||||
|
|
||||||
|
SPA Navigation via Link Clicks
|
||||||
|
Do not use element.click() on sidebar links from page.evaluate().
|
||||||
|
jQuery's .click() override may not fire correctly through
|
||||||
|
Playwright's evaluation bridge. Use Spa.dispatch() directly.
|
||||||
|
|
||||||
|
Checking Visibility by DOM Presence
|
||||||
|
Many RSX components (loading overlays, spinners) are always in
|
||||||
|
the DOM. They toggle visibility via CSS classes like 'is-visible'
|
||||||
|
or opacity changes. Check for the class, not the element:
|
||||||
|
|
||||||
|
// WRONG: element always exists
|
||||||
|
!!document.querySelector('.datagrid-loading-overlay')
|
||||||
|
|
||||||
|
// RIGHT: check for the visibility class
|
||||||
|
!!document.querySelector('.datagrid-loading-overlay.is-visible')
|
||||||
|
|
||||||
|
Execution Context Destroyed
|
||||||
|
Any page.evaluate() during SPA navigation can throw
|
||||||
|
"Execution context was destroyed". This is normal -- the SPA
|
||||||
|
replaced the DOM. Use try/catch or the addInitScript approach
|
||||||
|
which is immune to context destruction.
|
||||||
|
|
||||||
|
addInitScript Reruns
|
||||||
|
addInitScript runs on every NEW page load, not on SPA navigation.
|
||||||
|
If you see your observer's timer reset to 0 mid-step, it means
|
||||||
|
a full page reload occurred (not an SPA transition). This is a
|
||||||
|
useful diagnostic signal -- SPA transitions should NOT cause
|
||||||
|
addInitScript to re-run.
|
||||||
|
|
||||||
|
Dynamic Row Recalculation
|
||||||
|
DataGrids with $rows="dynamic" measure the viewport after first
|
||||||
|
render and may change per_page, triggering a second load. This
|
||||||
|
is normal on first visit but should be cached on return visits.
|
||||||
|
Expect to see two data states (e.g., DATA(10) then DATA(17))
|
||||||
|
as the grid adjusts.
|
||||||
|
|
||||||
|
Arbitrary Timeouts Instead of Lifecycle Events
|
||||||
|
Do NOT use page.waitForTimeout() or page.waitForLoadState()
|
||||||
|
as primary wait mechanisms. Use RSX lifecycle events:
|
||||||
|
- _debug_ready for initial page load
|
||||||
|
- spa_dispatch_ready for SPA navigations
|
||||||
|
|
||||||
|
Only use waitForTimeout for the dynamic height settle period
|
||||||
|
(500ms after ready event), where no lifecycle event exists.
|
||||||
|
|
||||||
|
FILES
|
||||||
|
system/bin/route-debug.js
|
||||||
|
Reference implementation for Playwright auth and single-page
|
||||||
|
testing. Source for the _debug_ready wait pattern.
|
||||||
|
|
||||||
|
system/app/RSpade/Core/Js/Rsx.js
|
||||||
|
Framework core. Contains validate_session() logic,
|
||||||
|
_debug_ready trigger, and init phase orchestration.
|
||||||
|
|
||||||
|
system/app/RSpade/Core/SPA/Spa.js
|
||||||
|
SPA router. Contains _on_spa_init() (awaited during init),
|
||||||
|
Spa.dispatch(), and spa_dispatch_ready event trigger.
|
||||||
|
|
||||||
|
system/node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
|
||||||
|
jqhtml component lifecycle, caching (Jqhtml_Local_Storage),
|
||||||
|
and Load_Coordinator. Understanding _load(), _reload(), and
|
||||||
|
the cache key generation is essential for diagnosing cache
|
||||||
|
miss issues.
|
||||||
|
|
||||||
|
rsx/theme/components/datagrid/datagrid_abstract.js
|
||||||
|
DataGrid state management, dynamic row calculation, and
|
||||||
|
load_page() orchestration. The parent component that drives
|
||||||
|
DataGrid_Table reloads.
|
||||||
|
|
||||||
|
rsx/theme/components/datagrid/datagrid_table.js
|
||||||
|
The child component whose on_load() actually fetches data.
|
||||||
|
Its args (per_page, sort, filter, etc.) form the cache key.
|
||||||
|
|
||||||
|
bin/test-datagrid-cache.js
|
||||||
|
Working reference test that validates DataGrid caching
|
||||||
|
across SPA navigations. Demonstrates all patterns from
|
||||||
|
this document.
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
rsx_debug, spa, jqhtml, caching
|
||||||
|
|
||||||
|
RSX Framework March 2026 PLAYWRIGHT_SPA(7)
|
||||||
@@ -502,7 +502,7 @@ COMMENTS IN TEMPLATES
|
|||||||
COMPONENT LIFECYCLE
|
COMPONENT LIFECYCLE
|
||||||
Five-stage deterministic lifecycle:
|
Five-stage deterministic lifecycle:
|
||||||
|
|
||||||
on_create → render → on_render → on_load → after_load → on_ready
|
on_create → render → on_render → on_load → on_loaded → on_ready
|
||||||
|
|
||||||
1. on_create() (synchronous, runs BEFORE first render)
|
1. on_create() (synchronous, runs BEFORE first render)
|
||||||
- Setup default state BEFORE template executes
|
- Setup default state BEFORE template executes
|
||||||
@@ -535,7 +535,7 @@ COMPONENT LIFECYCLE
|
|||||||
- If this.data changes, triggers automatic re-render
|
- If this.data changes, triggers automatic re-render
|
||||||
- Runtime enforces access restrictions with clear errors
|
- Runtime enforces access restrictions with clear errors
|
||||||
|
|
||||||
5. after_load() (runs on REAL component, not detached proxy)
|
5. on_loaded() (runs on REAL component, not detached proxy)
|
||||||
- this.data is frozen (read-only)
|
- this.data is frozen (read-only)
|
||||||
- this.$, this.state, this.args are accessible
|
- this.$, this.state, this.args are accessible
|
||||||
- Primary use case: clone this.data to this.state for widgets
|
- Primary use case: clone this.data to this.state for widgets
|
||||||
@@ -561,7 +561,7 @@ COMPONENT LIFECYCLE
|
|||||||
- on_load() runs in parallel for siblings (DOM unpredictable)
|
- on_load() runs in parallel for siblings (DOM unpredictable)
|
||||||
- Data changes during load trigger automatic re-render
|
- Data changes during load trigger automatic re-render
|
||||||
- on_create(), on_render(), on_stop() must be synchronous
|
- on_create(), on_render(), on_stop() must be synchronous
|
||||||
- on_load(), after_load(), and on_ready() can be async
|
- on_load(), on_loaded(), and on_ready() can be async
|
||||||
|
|
||||||
LIFECYCLE SKIP FLAGS
|
LIFECYCLE SKIP FLAGS
|
||||||
Two flags truncate the component lifecycle. Both cascade to children
|
Two flags truncate the component lifecycle. Both cascade to children
|
||||||
@@ -573,7 +573,7 @@ LIFECYCLE SKIP FLAGS
|
|||||||
Lifecycle:
|
Lifecycle:
|
||||||
on_create() → on_load() → READY
|
on_create() → on_load() → READY
|
||||||
|
|
||||||
Skips: render, on_render, after_load, on_ready
|
Skips: render, on_render, on_loaded, on_ready
|
||||||
No child components are created (render never runs).
|
No child components are created (render never runs).
|
||||||
|
|
||||||
Use case: Loading data from a component without creating its UI.
|
Use case: Loading data from a component without creating its UI.
|
||||||
@@ -589,7 +589,7 @@ LIFECYCLE SKIP FLAGS
|
|||||||
Lifecycle:
|
Lifecycle:
|
||||||
on_create() → render → on_load() → re-render → READY
|
on_create() → render → on_load() → re-render → READY
|
||||||
|
|
||||||
Skips: on_render, after_load, on_ready
|
Skips: on_render, on_loaded, on_ready
|
||||||
Children ARE created (render runs), and their on_load() fires too.
|
Children ARE created (render runs), and their on_load() fires too.
|
||||||
The full component tree is built with loaded data, but no DOM
|
The full component tree is built with loaded data, but no DOM
|
||||||
interaction (event handlers, plugin init, layout measurement).
|
interaction (event handlers, plugin init, layout measurement).
|
||||||
@@ -1353,7 +1353,7 @@ SYNCHRONOUS REQUIREMENTS
|
|||||||
on_render() YES NO
|
on_render() YES NO
|
||||||
on_stop() YES NO
|
on_stop() YES NO
|
||||||
on_load() NO (async allowed) YES
|
on_load() NO (async allowed) YES
|
||||||
after_load() NO (async allowed) YES
|
on_loaded() NO (async allowed) YES
|
||||||
on_ready() NO (async allowed) YES
|
on_ready() NO (async allowed) YES
|
||||||
|
|
||||||
Framework needs predictable execution order for lifecycle coordination.
|
Framework needs predictable execution order for lifecycle coordination.
|
||||||
|
|||||||
@@ -931,39 +931,41 @@ COMMON PATTERNS
|
|||||||
class Frontend_Contacts_Action extends Spa_Action { }
|
class Frontend_Contacts_Action extends Spa_Action { }
|
||||||
|
|
||||||
DETACHED ACTION LOADING
|
DETACHED ACTION LOADING
|
||||||
Spa.load_detached_action() loads an action without affecting the live SPA state.
|
Spa.load_detached_action(url, extra_args, options) loads an action without
|
||||||
The action is instantiated on a detached DOM element with the _load_only flag
|
affecting the live SPA state. Returns a fully-loaded component instance.
|
||||||
(on_create + on_load only, no render/children), and returns the component
|
|
||||||
instance for inspection.
|
Parameters:
|
||||||
|
url - URL to resolve and load
|
||||||
|
extra_args - Optional args merged with URL-extracted params
|
||||||
|
options - Optional behavior options:
|
||||||
|
load_children: false (default) Action on_load() only (_load_only)
|
||||||
|
load_children: true Full tree: render children, all on_load()s
|
||||||
|
fire (_load_render_only). Use for preloading.
|
||||||
|
|
||||||
Use cases:
|
Use cases:
|
||||||
- Extracting action metadata (titles, breadcrumbs) for navigation UI
|
- Extracting action metadata (titles, breadcrumbs) for navigation UI
|
||||||
- Pre-fetching action data before navigation
|
- Pre-fetching action data before navigation
|
||||||
- Inspecting action state without rendering it visibly
|
- Preloading entire page tree to warm ORM/Ajax cache
|
||||||
|
|
||||||
Basic Usage:
|
Basic Usage:
|
||||||
const action = await Spa.load_detached_action('/contacts/123');
|
const action = await Spa.load_detached_action('/contacts/123');
|
||||||
if (action) {
|
if (action) {
|
||||||
const title = action.get_title?.() ?? action.constructor.name;
|
const title = action.get_title?.() ?? action.constructor.name;
|
||||||
const breadcrumbs = action.get_breadcrumbs?.();
|
|
||||||
console.log('Page title:', title);
|
console.log('Page title:', title);
|
||||||
|
|
||||||
// IMPORTANT: Clean up when done to prevent memory leaks
|
|
||||||
action.stop();
|
action.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
With Cached Data:
|
With Cached Data:
|
||||||
// Skip network request if cached data available
|
|
||||||
const action = await Spa.load_detached_action('/contacts/123', {
|
const action = await Spa.load_detached_action('/contacts/123', {
|
||||||
use_cached_data: true
|
use_cached_data: true
|
||||||
});
|
});
|
||||||
|
|
||||||
Extra Arguments:
|
Preload With Children (warm full page cache):
|
||||||
// Pass additional args merged with URL-extracted params
|
// Renders children (datagrids, views, etc.) so their on_load()
|
||||||
const action = await Spa.load_detached_action('/contacts/123', {
|
// fires and populates the cache. No DOM hooks execute.
|
||||||
some_option: true,
|
const action = await Spa.load_detached_action('/contacts/123',
|
||||||
use_cached_data: true
|
{}, { load_children: true });
|
||||||
});
|
action.stop();
|
||||||
|
|
||||||
What It Does NOT Affect:
|
What It Does NOT Affect:
|
||||||
- Spa.action() (current live action remains unchanged)
|
- Spa.action() (current live action remains unchanged)
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ class Toggle_Button extends Component {
|
|||||||
2. **render** → Template executes (top-down: parent before children)
|
2. **render** → Template executes (top-down: parent before children)
|
||||||
3. **on_render()** → Fires after render, BEFORE children ready (top-down, sync)
|
3. **on_render()** → Fires after render, BEFORE children ready (top-down, sync)
|
||||||
4. **on_load()** → Fetch data into `this.data` (bottom-up, parallel siblings, async)
|
4. **on_load()** → Fetch data into `this.data` (bottom-up, parallel siblings, async)
|
||||||
5. **after_load()** → Runs on real component (not proxy). `this.data` frozen, `this.$`/`this.state`/`this.args` accessible. Clone `this.data` → `this.state` for complex in-memory manipulations.
|
5. **on_loaded()** → Runs on real component (not proxy). `this.data` frozen, `this.$`/`this.state`/`this.args` accessible. Clone `this.data` → `this.state` for complex in-memory manipulations.
|
||||||
6. **on_ready()** → All children guaranteed ready (bottom-up, async)
|
6. **on_ready()** → All children guaranteed ready (bottom-up, async)
|
||||||
7. **on_stop()** → Teardown when destroyed (sync)
|
7. **on_stop()** → Teardown when destroyed (sync)
|
||||||
|
|
||||||
|
|||||||
24
node_modules/.package-lock.json
generated
vendored
24
node_modules/.package-lock.json
generated
vendored
@@ -2224,9 +2224,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/core": {
|
"node_modules/@jqhtml/core": {
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/core/-/core-2.3.38.tgz",
|
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/core/-/core-2.3.39.tgz",
|
||||||
"integrity": "sha512-7yjkqgYAPuyl9bjw67nm7+NwDTR4nCuem9IHmW99SO5o/iJIuJUB9txuBQBo8l2g+pNbDFFI4wvUxJbFn3fznA==",
|
"integrity": "sha512-qyxOBcoFCaf35etqvNOSJppqT4WQLfD9O2b8bAv5la4oSpRUmXSjVJFdv3cSMIK8qClXbupN8bm4FLbAalJqog==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
@@ -2250,9 +2250,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/parser": {
|
"node_modules/@jqhtml/parser": {
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/parser/-/parser-2.3.38.tgz",
|
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/parser/-/parser-2.3.39.tgz",
|
||||||
"integrity": "sha512-kIb9u3p01FDTvQbq7LKmSBaGd5JZzJGZNL7oHQXzjSkvpL6/iTE2NXkOHI/0yiSfMR5s8tsMABnHQX8M8bzZJw==",
|
"integrity": "sha512-DLPwZf1X7enf2lVOaFaIWlu8vQYMgk/+Lioup2w4F07oXFx2+MnFgcJ/Ie9Pf6VUnMT1IOIZQxOd/5QugwFFDA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.11",
|
||||||
@@ -2290,9 +2290,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/ssr": {
|
"node_modules/@jqhtml/ssr": {
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.38.tgz",
|
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.39.tgz",
|
||||||
"integrity": "sha512-C0hFuzMVwAoKGGj2UvBkUqvNa2Q2Q95DYHuGMx+4cD0kCwxA2bpo5MnjcmtSQh2YX4UODKTaqmpHL+03MYOp7g==",
|
"integrity": "sha512-//MaIub8tel8w6l3AiqvoW021Aj9JR8BlVrZsezAO7svAIgsMFTeFdLKUud1+rg8I5Nxe4DE8CiGHz+f3Ts0kA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
@@ -2386,9 +2386,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/vscode-extension": {
|
"node_modules/@jqhtml/vscode-extension": {
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.38.tgz",
|
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.39.tgz",
|
||||||
"integrity": "sha512-/npaSwR6ibKl3z8xFlnMO1salWyzQIgs+1D7JH2QFi62o9TtIJWDhKKO/n2njc6PdUrJcDy6ElT1jYZh4eoNGg==",
|
"integrity": "sha512-Zi0iS5/t+5IhQoZP54J1/OOFB2OdoM6TM3g37SMJmPKjIDBUt883M3POszKFJwfj8+lrBV5OeJPOmPu3m9RYOQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.74.0"
|
"vscode": "^1.74.0"
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/component-cache.d.ts
generated
vendored
2
node_modules/@jqhtml/core/dist/component-cache.d.ts
generated
vendored
@@ -51,6 +51,6 @@ export declare function write_html_cache_snapshot(component: any): void;
|
|||||||
* @param component - The component instance
|
* @param component - The component instance
|
||||||
* @param data_changed - Whether this.data changed during on_load
|
* @param data_changed - Whether this.data changed during on_load
|
||||||
*/
|
*/
|
||||||
export declare function write_cache_after_load(component: any, data_changed: boolean): void;
|
export declare function write_cache_on_loaded(component: any, data_changed: boolean): void;
|
||||||
export {};
|
export {};
|
||||||
//# sourceMappingURL=component-cache.d.ts.map
|
//# sourceMappingURL=component-cache.d.ts.map
|
||||||
2
node_modules/@jqhtml/core/dist/component-cache.d.ts.map
generated
vendored
2
node_modules/@jqhtml/core/dist/component-cache.d.ts.map
generated
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"component-cache.d.ts","sourceRoot":"","sources":["../src/component-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH;;GAEG;AACH,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,GAAG,GAAG,gBAAgB,CAanE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI,CA+FzD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAqD7D;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI,CA2B9D;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,GAAG,IAAI,CA4BlF"}
|
{"version":3,"file":"component-cache.d.ts","sourceRoot":"","sources":["../src/component-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH;;GAEG;AACH,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,GAAG,GAAG,gBAAgB,CAanE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI,CA+FzD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAqD7D;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI,CA2B9D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,GAAG,IAAI,CA4BjF"}
|
||||||
3
node_modules/@jqhtml/core/dist/component.d.ts
generated
vendored
3
node_modules/@jqhtml/core/dist/component.d.ts
generated
vendored
@@ -52,6 +52,7 @@ export declare class Jqhtml_Component {
|
|||||||
private _use_cached_data_hit;
|
private _use_cached_data_hit;
|
||||||
private _load_only;
|
private _load_only;
|
||||||
private _load_render_only;
|
private _load_render_only;
|
||||||
|
_is_detached: boolean;
|
||||||
private _has_rendered;
|
private _has_rendered;
|
||||||
private _load_queue;
|
private _load_queue;
|
||||||
private __has_custom_on_load;
|
private __has_custom_on_load;
|
||||||
@@ -310,7 +311,7 @@ export declare class Jqhtml_Component {
|
|||||||
on_render(): void;
|
on_render(): void;
|
||||||
on_create(): void;
|
on_create(): void;
|
||||||
on_load(): void | Promise<void>;
|
on_load(): void | Promise<void>;
|
||||||
after_load(): void | Promise<void>;
|
on_loaded(): void | Promise<void>;
|
||||||
on_ready(): Promise<void>;
|
on_ready(): Promise<void>;
|
||||||
on_stop(): void;
|
on_stop(): void;
|
||||||
/**
|
/**
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/component.d.ts.map
generated
vendored
2
node_modules/@jqhtml/core/dist/component.d.ts.map
generated
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"}
|
||||||
59
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
59
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
@@ -39,8 +39,8 @@ class LifecycleManager {
|
|||||||
* Called when component is created
|
* Called when component is created
|
||||||
*
|
*
|
||||||
* Supports lifecycle truncation flags:
|
* Supports lifecycle truncation flags:
|
||||||
* - _load_only: on_create + on_load only. No render, no children, no on_render, no after_load, no on_ready.
|
* - _load_only: on_create + on_load only. No render, no children, no on_render, no on_loaded, no on_ready.
|
||||||
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no after_load, no on_ready.
|
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no on_loaded, no on_ready.
|
||||||
*/
|
*/
|
||||||
async boot_component(component) {
|
async boot_component(component) {
|
||||||
this.active_components.add(component);
|
this.active_components.add(component);
|
||||||
@@ -55,9 +55,10 @@ class LifecycleManager {
|
|||||||
// Check for lifecycle truncation flags
|
// Check for lifecycle truncation flags
|
||||||
const load_only = component._load_only;
|
const load_only = component._load_only;
|
||||||
const load_render_only = component._load_render_only;
|
const load_render_only = component._load_render_only;
|
||||||
|
const is_detached = component._is_detached;
|
||||||
let render_id;
|
let render_id;
|
||||||
if (load_only) {
|
if (load_only || is_detached) {
|
||||||
// _load_only: skip render entirely, no children created
|
// _load_only or detached: skip initial render entirely
|
||||||
render_id = 0;
|
render_id = 0;
|
||||||
component._render_count = 0;
|
component._render_count = 0;
|
||||||
}
|
}
|
||||||
@@ -84,9 +85,6 @@ class LifecycleManager {
|
|||||||
// even if on_load() itself completes instantly (deterministic behavior)
|
// even if on_load() itself completes instantly (deterministic behavior)
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
}
|
}
|
||||||
// Trigger 'loaded' event - fires after on_load completes (or would have completed)
|
|
||||||
// This happens before the render/on_render second step
|
|
||||||
component.trigger('loaded');
|
|
||||||
// Check if stopped during load
|
// Check if stopped during load
|
||||||
if (component._stopped)
|
if (component._stopped)
|
||||||
return;
|
return;
|
||||||
@@ -98,8 +96,9 @@ class LifecycleManager {
|
|||||||
component.trigger('ready');
|
component.trigger('ready');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If data changed during load, re-render
|
// Detached elements always render here (initial render was skipped)
|
||||||
if (component._should_rerender()) {
|
// Normal elements re-render only if data changed during on_load
|
||||||
|
if (is_detached || component._should_rerender()) {
|
||||||
// Disable animations during re-render to prevent visual artifacts
|
// Disable animations during re-render to prevent visual artifacts
|
||||||
// from CSS transitions/animations firing on newly rendered elements
|
// from CSS transitions/animations firing on newly rendered elements
|
||||||
const $el = component.$;
|
const $el = component.$;
|
||||||
@@ -2659,7 +2658,7 @@ function write_html_cache_snapshot(component) {
|
|||||||
* @param component - The component instance
|
* @param component - The component instance
|
||||||
* @param data_changed - Whether this.data changed during on_load
|
* @param data_changed - Whether this.data changed during on_load
|
||||||
*/
|
*/
|
||||||
function write_cache_after_load(component, data_changed) {
|
function write_cache_on_loaded(component, data_changed) {
|
||||||
if (!data_changed || !component._cache_key) {
|
if (!data_changed || !component._cache_key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2842,10 +2841,13 @@ class Jqhtml_Component {
|
|||||||
this._on_render_complete = false; // True after on_render() has been called post-on_load
|
this._on_render_complete = false; // True after on_render() has been called post-on_load
|
||||||
// use_cached_data feature - skip on_load() when cache hit occurs
|
// use_cached_data feature - skip on_load() when cache hit occurs
|
||||||
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
|
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
|
||||||
// _load_only: create + load only. No render, no children, no on_render, no after_load, no on_ready.
|
// _load_only: create + load only. No render, no children, no on_render, no on_loaded, no on_ready.
|
||||||
this._load_only = false;
|
this._load_only = false;
|
||||||
// _load_render_only: create + render + load + re-render. No on_render, no after_load, no on_ready.
|
// _load_render_only: create + render + load + re-render. No on_render, no on_loaded, no on_ready.
|
||||||
this._load_render_only = false;
|
this._load_render_only = false;
|
||||||
|
// Detached optimization: true when element is not in the DOM at boot time.
|
||||||
|
// Skips initial render and cache read — just on_load then render.
|
||||||
|
this._is_detached = false;
|
||||||
// rendered event - fires once after the synchronous render chain completes
|
// rendered event - fires once after the synchronous render chain completes
|
||||||
// (after on_load's re-render if applicable, or after first render if no on_load)
|
// (after on_load's re-render if applicable, or after first render if no on_load)
|
||||||
this._has_rendered = false;
|
this._has_rendered = false;
|
||||||
@@ -3390,11 +3392,11 @@ class Jqhtml_Component {
|
|||||||
Jqhtml_Local_Storage.set(cache_key, this.data);
|
Jqhtml_Local_Storage.set(cache_key, this.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Call after_load() on the real component (this.data frozen, full access to this.$, this.state)
|
// Call on_loaded() on the real component (this.data frozen, full access to this.$, this.state)
|
||||||
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data_changed;
|
return data_changed;
|
||||||
@@ -3414,12 +3416,17 @@ class Jqhtml_Component {
|
|||||||
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
|
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
|
||||||
// Don't await - on_create MUST be sync. The warning is enough.
|
// Don't await - on_create MUST be sync. The warning is enough.
|
||||||
}
|
}
|
||||||
|
// Detect detached elements — skip cache and initial render for elements not in DOM
|
||||||
|
this._is_detached = !this.$[0].isConnected;
|
||||||
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
|
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
|
||||||
// Components without on_load() don't fetch data, so nothing to cache or restore
|
// Components without on_load() don't fetch data, so nothing to cache or restore
|
||||||
if (this.__has_custom_on_load) {
|
if (this.__has_custom_on_load) {
|
||||||
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
|
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
|
||||||
|
// Skip cache for detached elements — no point hydrating from cache when not in DOM
|
||||||
// @see component-cache.ts for full implementation
|
// @see component-cache.ts for full implementation
|
||||||
read_cache_in_create(this);
|
if (!this._is_detached) {
|
||||||
|
read_cache_in_create(this);
|
||||||
|
}
|
||||||
// Snapshot this.data after on_create() completes
|
// Snapshot this.data after on_create() completes
|
||||||
// This will be restored before each on_load() execution to reset state
|
// This will be restored before each on_load() execution to reset state
|
||||||
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
|
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
|
||||||
@@ -3455,8 +3462,8 @@ class Jqhtml_Component {
|
|||||||
this._log_lifecycle('load', 'complete (use_cached_data - skipped on_load)');
|
this._log_lifecycle('load', 'complete (use_cached_data - skipped on_load)');
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3535,8 +3542,8 @@ class Jqhtml_Component {
|
|||||||
this._log_lifecycle('load', 'complete (follower)');
|
this._log_lifecycle('load', 'complete (follower)');
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3605,18 +3612,18 @@ class Jqhtml_Component {
|
|||||||
// Used by HTML cache mode for synchronization - static parents don't block children
|
// Used by HTML cache mode for synchronization - static parents don't block children
|
||||||
this._is_dynamic = data_changed && data_after_load !== '{}';
|
this._is_dynamic = data_changed && data_after_load !== '{}';
|
||||||
// CACHE WRITE - @see component-cache.ts
|
// CACHE WRITE - @see component-cache.ts
|
||||||
write_cache_after_load(this, this._is_dynamic);
|
write_cache_on_loaded(this, this._is_dynamic);
|
||||||
this._ready_state = 2;
|
this._ready_state = 2;
|
||||||
this._update_debug_attrs();
|
this._update_debug_attrs();
|
||||||
this._log_lifecycle('load', 'complete');
|
this._log_lifecycle('load', 'complete');
|
||||||
// Emit lifecycle event
|
// Emit lifecycle event
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
// Call after_load() - runs on the REAL component (not detached proxy)
|
// Call on_loaded() - runs on the REAL component (not detached proxy)
|
||||||
// this.data is frozen (read-only), but this.$, this.state, this.args are accessible
|
// this.data is frozen (read-only), but this.$, this.state, this.args are accessible
|
||||||
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@@ -4021,7 +4028,7 @@ class Jqhtml_Component {
|
|||||||
on_render() { }
|
on_render() { }
|
||||||
on_create() { }
|
on_create() { }
|
||||||
on_load() { } // Override to fetch data asynchronously
|
on_load() { } // Override to fetch data asynchronously
|
||||||
after_load() { } // Override to process loaded data (e.g., clone this.data to this.state)
|
on_loaded() { } // Override to process loaded data (e.g., clone this.data to this.state)
|
||||||
async on_ready() { }
|
async on_ready() { }
|
||||||
on_stop() { }
|
on_stop() { }
|
||||||
/**
|
/**
|
||||||
@@ -5273,7 +5280,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.38';
|
const version = '2.3.39';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/index.cjs.map
generated
vendored
2
node_modules/@jqhtml/core/dist/index.cjs.map
generated
vendored
File diff suppressed because one or more lines are too long
59
node_modules/@jqhtml/core/dist/index.js
generated
vendored
59
node_modules/@jqhtml/core/dist/index.js
generated
vendored
@@ -35,8 +35,8 @@ class LifecycleManager {
|
|||||||
* Called when component is created
|
* Called when component is created
|
||||||
*
|
*
|
||||||
* Supports lifecycle truncation flags:
|
* Supports lifecycle truncation flags:
|
||||||
* - _load_only: on_create + on_load only. No render, no children, no on_render, no after_load, no on_ready.
|
* - _load_only: on_create + on_load only. No render, no children, no on_render, no on_loaded, no on_ready.
|
||||||
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no after_load, no on_ready.
|
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no on_loaded, no on_ready.
|
||||||
*/
|
*/
|
||||||
async boot_component(component) {
|
async boot_component(component) {
|
||||||
this.active_components.add(component);
|
this.active_components.add(component);
|
||||||
@@ -51,9 +51,10 @@ class LifecycleManager {
|
|||||||
// Check for lifecycle truncation flags
|
// Check for lifecycle truncation flags
|
||||||
const load_only = component._load_only;
|
const load_only = component._load_only;
|
||||||
const load_render_only = component._load_render_only;
|
const load_render_only = component._load_render_only;
|
||||||
|
const is_detached = component._is_detached;
|
||||||
let render_id;
|
let render_id;
|
||||||
if (load_only) {
|
if (load_only || is_detached) {
|
||||||
// _load_only: skip render entirely, no children created
|
// _load_only or detached: skip initial render entirely
|
||||||
render_id = 0;
|
render_id = 0;
|
||||||
component._render_count = 0;
|
component._render_count = 0;
|
||||||
}
|
}
|
||||||
@@ -80,9 +81,6 @@ class LifecycleManager {
|
|||||||
// even if on_load() itself completes instantly (deterministic behavior)
|
// even if on_load() itself completes instantly (deterministic behavior)
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
}
|
}
|
||||||
// Trigger 'loaded' event - fires after on_load completes (or would have completed)
|
|
||||||
// This happens before the render/on_render second step
|
|
||||||
component.trigger('loaded');
|
|
||||||
// Check if stopped during load
|
// Check if stopped during load
|
||||||
if (component._stopped)
|
if (component._stopped)
|
||||||
return;
|
return;
|
||||||
@@ -94,8 +92,9 @@ class LifecycleManager {
|
|||||||
component.trigger('ready');
|
component.trigger('ready');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If data changed during load, re-render
|
// Detached elements always render here (initial render was skipped)
|
||||||
if (component._should_rerender()) {
|
// Normal elements re-render only if data changed during on_load
|
||||||
|
if (is_detached || component._should_rerender()) {
|
||||||
// Disable animations during re-render to prevent visual artifacts
|
// Disable animations during re-render to prevent visual artifacts
|
||||||
// from CSS transitions/animations firing on newly rendered elements
|
// from CSS transitions/animations firing on newly rendered elements
|
||||||
const $el = component.$;
|
const $el = component.$;
|
||||||
@@ -2655,7 +2654,7 @@ function write_html_cache_snapshot(component) {
|
|||||||
* @param component - The component instance
|
* @param component - The component instance
|
||||||
* @param data_changed - Whether this.data changed during on_load
|
* @param data_changed - Whether this.data changed during on_load
|
||||||
*/
|
*/
|
||||||
function write_cache_after_load(component, data_changed) {
|
function write_cache_on_loaded(component, data_changed) {
|
||||||
if (!data_changed || !component._cache_key) {
|
if (!data_changed || !component._cache_key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2838,10 +2837,13 @@ class Jqhtml_Component {
|
|||||||
this._on_render_complete = false; // True after on_render() has been called post-on_load
|
this._on_render_complete = false; // True after on_render() has been called post-on_load
|
||||||
// use_cached_data feature - skip on_load() when cache hit occurs
|
// use_cached_data feature - skip on_load() when cache hit occurs
|
||||||
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
|
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
|
||||||
// _load_only: create + load only. No render, no children, no on_render, no after_load, no on_ready.
|
// _load_only: create + load only. No render, no children, no on_render, no on_loaded, no on_ready.
|
||||||
this._load_only = false;
|
this._load_only = false;
|
||||||
// _load_render_only: create + render + load + re-render. No on_render, no after_load, no on_ready.
|
// _load_render_only: create + render + load + re-render. No on_render, no on_loaded, no on_ready.
|
||||||
this._load_render_only = false;
|
this._load_render_only = false;
|
||||||
|
// Detached optimization: true when element is not in the DOM at boot time.
|
||||||
|
// Skips initial render and cache read — just on_load then render.
|
||||||
|
this._is_detached = false;
|
||||||
// rendered event - fires once after the synchronous render chain completes
|
// rendered event - fires once after the synchronous render chain completes
|
||||||
// (after on_load's re-render if applicable, or after first render if no on_load)
|
// (after on_load's re-render if applicable, or after first render if no on_load)
|
||||||
this._has_rendered = false;
|
this._has_rendered = false;
|
||||||
@@ -3386,11 +3388,11 @@ class Jqhtml_Component {
|
|||||||
Jqhtml_Local_Storage.set(cache_key, this.data);
|
Jqhtml_Local_Storage.set(cache_key, this.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Call after_load() on the real component (this.data frozen, full access to this.$, this.state)
|
// Call on_loaded() on the real component (this.data frozen, full access to this.$, this.state)
|
||||||
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data_changed;
|
return data_changed;
|
||||||
@@ -3410,12 +3412,17 @@ class Jqhtml_Component {
|
|||||||
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
|
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
|
||||||
// Don't await - on_create MUST be sync. The warning is enough.
|
// Don't await - on_create MUST be sync. The warning is enough.
|
||||||
}
|
}
|
||||||
|
// Detect detached elements — skip cache and initial render for elements not in DOM
|
||||||
|
this._is_detached = !this.$[0].isConnected;
|
||||||
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
|
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
|
||||||
// Components without on_load() don't fetch data, so nothing to cache or restore
|
// Components without on_load() don't fetch data, so nothing to cache or restore
|
||||||
if (this.__has_custom_on_load) {
|
if (this.__has_custom_on_load) {
|
||||||
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
|
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
|
||||||
|
// Skip cache for detached elements — no point hydrating from cache when not in DOM
|
||||||
// @see component-cache.ts for full implementation
|
// @see component-cache.ts for full implementation
|
||||||
read_cache_in_create(this);
|
if (!this._is_detached) {
|
||||||
|
read_cache_in_create(this);
|
||||||
|
}
|
||||||
// Snapshot this.data after on_create() completes
|
// Snapshot this.data after on_create() completes
|
||||||
// This will be restored before each on_load() execution to reset state
|
// This will be restored before each on_load() execution to reset state
|
||||||
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
|
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
|
||||||
@@ -3451,8 +3458,8 @@ class Jqhtml_Component {
|
|||||||
this._log_lifecycle('load', 'complete (use_cached_data - skipped on_load)');
|
this._log_lifecycle('load', 'complete (use_cached_data - skipped on_load)');
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3531,8 +3538,8 @@ class Jqhtml_Component {
|
|||||||
this._log_lifecycle('load', 'complete (follower)');
|
this._log_lifecycle('load', 'complete (follower)');
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3601,18 +3608,18 @@ class Jqhtml_Component {
|
|||||||
// Used by HTML cache mode for synchronization - static parents don't block children
|
// Used by HTML cache mode for synchronization - static parents don't block children
|
||||||
this._is_dynamic = data_changed && data_after_load !== '{}';
|
this._is_dynamic = data_changed && data_after_load !== '{}';
|
||||||
// CACHE WRITE - @see component-cache.ts
|
// CACHE WRITE - @see component-cache.ts
|
||||||
write_cache_after_load(this, this._is_dynamic);
|
write_cache_on_loaded(this, this._is_dynamic);
|
||||||
this._ready_state = 2;
|
this._ready_state = 2;
|
||||||
this._update_debug_attrs();
|
this._update_debug_attrs();
|
||||||
this._log_lifecycle('load', 'complete');
|
this._log_lifecycle('load', 'complete');
|
||||||
// Emit lifecycle event
|
// Emit lifecycle event
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
// Call after_load() - runs on the REAL component (not detached proxy)
|
// Call on_loaded() - runs on the REAL component (not detached proxy)
|
||||||
// this.data is frozen (read-only), but this.$, this.state, this.args are accessible
|
// this.data is frozen (read-only), but this.$, this.state, this.args are accessible
|
||||||
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@@ -4017,7 +4024,7 @@ class Jqhtml_Component {
|
|||||||
on_render() { }
|
on_render() { }
|
||||||
on_create() { }
|
on_create() { }
|
||||||
on_load() { } // Override to fetch data asynchronously
|
on_load() { } // Override to fetch data asynchronously
|
||||||
after_load() { } // Override to process loaded data (e.g., clone this.data to this.state)
|
on_loaded() { } // Override to process loaded data (e.g., clone this.data to this.state)
|
||||||
async on_ready() { }
|
async on_ready() { }
|
||||||
on_stop() { }
|
on_stop() { }
|
||||||
/**
|
/**
|
||||||
@@ -5269,7 +5276,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.38';
|
const version = '2.3.39';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/index.js.map
generated
vendored
2
node_modules/@jqhtml/core/dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
61
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
61
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* JQHTML Core v2.3.38
|
* JQHTML Core v2.3.39
|
||||||
* (c) 2025 JQHTML Team
|
* (c) 2025 JQHTML Team
|
||||||
* Released under the MIT License
|
* Released under the MIT License
|
||||||
*/
|
*/
|
||||||
@@ -40,8 +40,8 @@ class LifecycleManager {
|
|||||||
* Called when component is created
|
* Called when component is created
|
||||||
*
|
*
|
||||||
* Supports lifecycle truncation flags:
|
* Supports lifecycle truncation flags:
|
||||||
* - _load_only: on_create + on_load only. No render, no children, no on_render, no after_load, no on_ready.
|
* - _load_only: on_create + on_load only. No render, no children, no on_render, no on_loaded, no on_ready.
|
||||||
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no after_load, no on_ready.
|
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no on_loaded, no on_ready.
|
||||||
*/
|
*/
|
||||||
async boot_component(component) {
|
async boot_component(component) {
|
||||||
this.active_components.add(component);
|
this.active_components.add(component);
|
||||||
@@ -56,9 +56,10 @@ class LifecycleManager {
|
|||||||
// Check for lifecycle truncation flags
|
// Check for lifecycle truncation flags
|
||||||
const load_only = component._load_only;
|
const load_only = component._load_only;
|
||||||
const load_render_only = component._load_render_only;
|
const load_render_only = component._load_render_only;
|
||||||
|
const is_detached = component._is_detached;
|
||||||
let render_id;
|
let render_id;
|
||||||
if (load_only) {
|
if (load_only || is_detached) {
|
||||||
// _load_only: skip render entirely, no children created
|
// _load_only or detached: skip initial render entirely
|
||||||
render_id = 0;
|
render_id = 0;
|
||||||
component._render_count = 0;
|
component._render_count = 0;
|
||||||
}
|
}
|
||||||
@@ -85,9 +86,6 @@ class LifecycleManager {
|
|||||||
// even if on_load() itself completes instantly (deterministic behavior)
|
// even if on_load() itself completes instantly (deterministic behavior)
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
}
|
}
|
||||||
// Trigger 'loaded' event - fires after on_load completes (or would have completed)
|
|
||||||
// This happens before the render/on_render second step
|
|
||||||
component.trigger('loaded');
|
|
||||||
// Check if stopped during load
|
// Check if stopped during load
|
||||||
if (component._stopped)
|
if (component._stopped)
|
||||||
return;
|
return;
|
||||||
@@ -99,8 +97,9 @@ class LifecycleManager {
|
|||||||
component.trigger('ready');
|
component.trigger('ready');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If data changed during load, re-render
|
// Detached elements always render here (initial render was skipped)
|
||||||
if (component._should_rerender()) {
|
// Normal elements re-render only if data changed during on_load
|
||||||
|
if (is_detached || component._should_rerender()) {
|
||||||
// Disable animations during re-render to prevent visual artifacts
|
// Disable animations during re-render to prevent visual artifacts
|
||||||
// from CSS transitions/animations firing on newly rendered elements
|
// from CSS transitions/animations firing on newly rendered elements
|
||||||
const $el = component.$;
|
const $el = component.$;
|
||||||
@@ -2660,7 +2659,7 @@ function write_html_cache_snapshot(component) {
|
|||||||
* @param component - The component instance
|
* @param component - The component instance
|
||||||
* @param data_changed - Whether this.data changed during on_load
|
* @param data_changed - Whether this.data changed during on_load
|
||||||
*/
|
*/
|
||||||
function write_cache_after_load(component, data_changed) {
|
function write_cache_on_loaded(component, data_changed) {
|
||||||
if (!data_changed || !component._cache_key) {
|
if (!data_changed || !component._cache_key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2843,10 +2842,13 @@ class Jqhtml_Component {
|
|||||||
this._on_render_complete = false; // True after on_render() has been called post-on_load
|
this._on_render_complete = false; // True after on_render() has been called post-on_load
|
||||||
// use_cached_data feature - skip on_load() when cache hit occurs
|
// use_cached_data feature - skip on_load() when cache hit occurs
|
||||||
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
|
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
|
||||||
// _load_only: create + load only. No render, no children, no on_render, no after_load, no on_ready.
|
// _load_only: create + load only. No render, no children, no on_render, no on_loaded, no on_ready.
|
||||||
this._load_only = false;
|
this._load_only = false;
|
||||||
// _load_render_only: create + render + load + re-render. No on_render, no after_load, no on_ready.
|
// _load_render_only: create + render + load + re-render. No on_render, no on_loaded, no on_ready.
|
||||||
this._load_render_only = false;
|
this._load_render_only = false;
|
||||||
|
// Detached optimization: true when element is not in the DOM at boot time.
|
||||||
|
// Skips initial render and cache read — just on_load then render.
|
||||||
|
this._is_detached = false;
|
||||||
// rendered event - fires once after the synchronous render chain completes
|
// rendered event - fires once after the synchronous render chain completes
|
||||||
// (after on_load's re-render if applicable, or after first render if no on_load)
|
// (after on_load's re-render if applicable, or after first render if no on_load)
|
||||||
this._has_rendered = false;
|
this._has_rendered = false;
|
||||||
@@ -3391,11 +3393,11 @@ class Jqhtml_Component {
|
|||||||
Jqhtml_Local_Storage.set(cache_key, this.data);
|
Jqhtml_Local_Storage.set(cache_key, this.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Call after_load() on the real component (this.data frozen, full access to this.$, this.state)
|
// Call on_loaded() on the real component (this.data frozen, full access to this.$, this.state)
|
||||||
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data_changed;
|
return data_changed;
|
||||||
@@ -3415,12 +3417,17 @@ class Jqhtml_Component {
|
|||||||
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
|
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
|
||||||
// Don't await - on_create MUST be sync. The warning is enough.
|
// Don't await - on_create MUST be sync. The warning is enough.
|
||||||
}
|
}
|
||||||
|
// Detect detached elements — skip cache and initial render for elements not in DOM
|
||||||
|
this._is_detached = !this.$[0].isConnected;
|
||||||
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
|
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
|
||||||
// Components without on_load() don't fetch data, so nothing to cache or restore
|
// Components without on_load() don't fetch data, so nothing to cache or restore
|
||||||
if (this.__has_custom_on_load) {
|
if (this.__has_custom_on_load) {
|
||||||
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
|
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
|
||||||
|
// Skip cache for detached elements — no point hydrating from cache when not in DOM
|
||||||
// @see component-cache.ts for full implementation
|
// @see component-cache.ts for full implementation
|
||||||
read_cache_in_create(this);
|
if (!this._is_detached) {
|
||||||
|
read_cache_in_create(this);
|
||||||
|
}
|
||||||
// Snapshot this.data after on_create() completes
|
// Snapshot this.data after on_create() completes
|
||||||
// This will be restored before each on_load() execution to reset state
|
// This will be restored before each on_load() execution to reset state
|
||||||
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
|
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
|
||||||
@@ -3456,8 +3463,8 @@ class Jqhtml_Component {
|
|||||||
this._log_lifecycle('load', 'complete (use_cached_data - skipped on_load)');
|
this._log_lifecycle('load', 'complete (use_cached_data - skipped on_load)');
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3536,8 +3543,8 @@ class Jqhtml_Component {
|
|||||||
this._log_lifecycle('load', 'complete (follower)');
|
this._log_lifecycle('load', 'complete (follower)');
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3606,18 +3613,18 @@ class Jqhtml_Component {
|
|||||||
// Used by HTML cache mode for synchronization - static parents don't block children
|
// Used by HTML cache mode for synchronization - static parents don't block children
|
||||||
this._is_dynamic = data_changed && data_after_load !== '{}';
|
this._is_dynamic = data_changed && data_after_load !== '{}';
|
||||||
// CACHE WRITE - @see component-cache.ts
|
// CACHE WRITE - @see component-cache.ts
|
||||||
write_cache_after_load(this, this._is_dynamic);
|
write_cache_on_loaded(this, this._is_dynamic);
|
||||||
this._ready_state = 2;
|
this._ready_state = 2;
|
||||||
this._update_debug_attrs();
|
this._update_debug_attrs();
|
||||||
this._log_lifecycle('load', 'complete');
|
this._log_lifecycle('load', 'complete');
|
||||||
// Emit lifecycle event
|
// Emit lifecycle event
|
||||||
this.trigger('load');
|
this.trigger('load');
|
||||||
// Call after_load() - runs on the REAL component (not detached proxy)
|
// Call on_loaded() - runs on the REAL component (not detached proxy)
|
||||||
// this.data is frozen (read-only), but this.$, this.state, this.args are accessible
|
// this.data is frozen (read-only), but this.$, this.state, this.args are accessible
|
||||||
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
// Suppressed by _load_only and _load_render_only flags (preloading mode)
|
||||||
if (!this._load_only && !this._load_render_only) {
|
if (!this._load_only && !this._load_render_only) {
|
||||||
await this._call_lifecycle('after_load');
|
await this._call_lifecycle('on_loaded');
|
||||||
this.trigger('after_load');
|
this.trigger('on_loaded');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@@ -4022,7 +4029,7 @@ class Jqhtml_Component {
|
|||||||
on_render() { }
|
on_render() { }
|
||||||
on_create() { }
|
on_create() { }
|
||||||
on_load() { } // Override to fetch data asynchronously
|
on_load() { } // Override to fetch data asynchronously
|
||||||
after_load() { } // Override to process loaded data (e.g., clone this.data to this.state)
|
on_loaded() { } // Override to process loaded data (e.g., clone this.data to this.state)
|
||||||
async on_ready() { }
|
async on_ready() { }
|
||||||
on_stop() { }
|
on_stop() { }
|
||||||
/**
|
/**
|
||||||
@@ -5274,7 +5281,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.38';
|
const version = '2.3.39';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js.map
generated
vendored
2
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js.map
generated
vendored
File diff suppressed because one or more lines are too long
4
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts
generated
vendored
4
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts
generated
vendored
@@ -27,8 +27,8 @@ export declare class LifecycleManager {
|
|||||||
* Called when component is created
|
* Called when component is created
|
||||||
*
|
*
|
||||||
* Supports lifecycle truncation flags:
|
* Supports lifecycle truncation flags:
|
||||||
* - _load_only: on_create + on_load only. No render, no children, no on_render, no after_load, no on_ready.
|
* - _load_only: on_create + on_load only. No render, no children, no on_render, no on_loaded, no on_ready.
|
||||||
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no after_load, no on_ready.
|
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no on_loaded, no on_ready.
|
||||||
*/
|
*/
|
||||||
boot_component(component: Jqhtml_Component): Promise<void>;
|
boot_component(component: Jqhtml_Component): Promise<void>;
|
||||||
/**
|
/**
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts.map
generated
vendored
2
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts.map
generated
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"lifecycle-manager.d.ts","sourceRoot":"","sources":["../src/lifecycle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmB;IAC1C,OAAO,CAAC,iBAAiB,CAAoC;IAE7D,MAAM,CAAC,YAAY,IAAI,gBAAgB;;IAevC;;;;;;;OAOG;IACG,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+IhE;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIvD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAetC"}
|
{"version":3,"file":"lifecycle-manager.d.ts","sourceRoot":"","sources":["../src/lifecycle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmB;IAC1C,OAAO,CAAC,iBAAiB,CAAoC;IAE7D,MAAM,CAAC,YAAY,IAAI,gBAAgB;;IAevC;;;;;;;OAOG;IACG,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6IhE;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIvD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAetC"}
|
||||||
2
node_modules/@jqhtml/core/package.json
generated
vendored
2
node_modules/@jqhtml/core/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/core",
|
"name": "@jqhtml/core",
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"description": "Core runtime library for JQHTML",
|
"description": "Core runtime library for JQHTML",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
@@ -1385,7 +1385,7 @@ export class CodeGenerator {
|
|||||||
for (const [name, component] of this.components) {
|
for (const [name, component] of this.components) {
|
||||||
code += `// Component: ${name}\n`;
|
code += `// Component: ${name}\n`;
|
||||||
code += `jqhtml_components.set('${name}', {\n`;
|
code += `jqhtml_components.set('${name}', {\n`;
|
||||||
code += ` _jqhtml_version: '2.3.38',\n`; // Version will be replaced during build
|
code += ` _jqhtml_version: '2.3.39',\n`; // Version will be replaced during build
|
||||||
code += ` name: '${name}',\n`;
|
code += ` name: '${name}',\n`;
|
||||||
code += ` tag: '${component.tagName}',\n`;
|
code += ` tag: '${component.tagName}',\n`;
|
||||||
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
||||||
|
|||||||
2
node_modules/@jqhtml/parser/package.json
generated
vendored
2
node_modules/@jqhtml/parser/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/parser",
|
"name": "@jqhtml/parser",
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"description": "JQHTML template parser - converts templates to JavaScript",
|
"description": "JQHTML template parser - converts templates to JavaScript",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
2
node_modules/@jqhtml/ssr/package.json
generated
vendored
2
node_modules/@jqhtml/ssr/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/ssr",
|
"name": "@jqhtml/ssr",
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"description": "Server-Side Rendering for JQHTML components - renders components to HTML for SEO",
|
"description": "Server-Side Rendering for JQHTML components - renders components to HTML for SEO",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
@@ -1 +1 @@
|
|||||||
2.3.38
|
2.3.39
|
||||||
|
|||||||
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.3.38.vsix → node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.3.39.vsix
generated
vendored
Normal file → Executable file
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.3.38.vsix → node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.3.39.vsix
generated
vendored
Normal file → Executable file
Binary file not shown.
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
@@ -2,7 +2,7 @@
|
|||||||
"name": "@jqhtml/vscode-extension",
|
"name": "@jqhtml/vscode-extension",
|
||||||
"displayName": "JQHTML",
|
"displayName": "JQHTML",
|
||||||
"description": "Syntax highlighting and language support for JQHTML template files",
|
"description": "Syntax highlighting and language support for JQHTML template files",
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"publisher": "jqhtml",
|
"publisher": "jqhtml",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
24
package-lock.json
generated
24
package-lock.json
generated
@@ -2676,9 +2676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/core": {
|
"node_modules/@jqhtml/core": {
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/core/-/core-2.3.38.tgz",
|
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/core/-/core-2.3.39.tgz",
|
||||||
"integrity": "sha512-7yjkqgYAPuyl9bjw67nm7+NwDTR4nCuem9IHmW99SO5o/iJIuJUB9txuBQBo8l2g+pNbDFFI4wvUxJbFn3fznA==",
|
"integrity": "sha512-qyxOBcoFCaf35etqvNOSJppqT4WQLfD9O2b8bAv5la4oSpRUmXSjVJFdv3cSMIK8qClXbupN8bm4FLbAalJqog==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
@@ -2702,9 +2702,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/parser": {
|
"node_modules/@jqhtml/parser": {
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/parser/-/parser-2.3.38.tgz",
|
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/parser/-/parser-2.3.39.tgz",
|
||||||
"integrity": "sha512-kIb9u3p01FDTvQbq7LKmSBaGd5JZzJGZNL7oHQXzjSkvpL6/iTE2NXkOHI/0yiSfMR5s8tsMABnHQX8M8bzZJw==",
|
"integrity": "sha512-DLPwZf1X7enf2lVOaFaIWlu8vQYMgk/+Lioup2w4F07oXFx2+MnFgcJ/Ie9Pf6VUnMT1IOIZQxOd/5QugwFFDA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.11",
|
||||||
@@ -2742,9 +2742,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/ssr": {
|
"node_modules/@jqhtml/ssr": {
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.38.tgz",
|
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.39.tgz",
|
||||||
"integrity": "sha512-C0hFuzMVwAoKGGj2UvBkUqvNa2Q2Q95DYHuGMx+4cD0kCwxA2bpo5MnjcmtSQh2YX4UODKTaqmpHL+03MYOp7g==",
|
"integrity": "sha512-//MaIub8tel8w6l3AiqvoW021Aj9JR8BlVrZsezAO7svAIgsMFTeFdLKUud1+rg8I5Nxe4DE8CiGHz+f3Ts0kA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
@@ -2838,9 +2838,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/vscode-extension": {
|
"node_modules/@jqhtml/vscode-extension": {
|
||||||
"version": "2.3.38",
|
"version": "2.3.39",
|
||||||
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.38.tgz",
|
"resolved": "http://npm.internal.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.39.tgz",
|
||||||
"integrity": "sha512-/npaSwR6ibKl3z8xFlnMO1salWyzQIgs+1D7JH2QFi62o9TtIJWDhKKO/n2njc6PdUrJcDy6ElT1jYZh4eoNGg==",
|
"integrity": "sha512-Zi0iS5/t+5IhQoZP54J1/OOFB2OdoM6TM3g37SMJmPKjIDBUt883M3POszKFJwfj8+lrBV5OeJPOmPu3m9RYOQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.74.0"
|
"vscode": "^1.74.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user