Uh oh!
There was an error while loading. Please reload this page.
fix(js): initialize a model added after the document is ready - #23
Conversation
"addModel" defers registration to "onDocumentReady", which only added a "DOMContentLoaded" listener. A form fragment that an application fetches and injects after the initial load runs its "addModel" call when that event has already been dispatched, so the listener was never called and the form kept no validator at all. The callback now runs immediately once "document.readyState" is past "loading", and still waits for the event while the document is parsing.
"onDocumentReady" ran the callback for every state past "loading", but "interactive" means the document is parsed while its deferred scripts, and the "js_validator_config()" one of them may carry, still run before "DOMContentLoaded". Only "complete" stands for an event that is really gone, so the call a model added with "onLoad = false" defers keeps waiting for the configuration it turns the native UI off with. The same condition is what old IE needs, where "interactive" does not mean the document can be walked yet. The listener the other branch adds is removable again. It passed the event it received to "removeEventListener", which identifies no listener, so every call left one behind, and the fallback ran its callback on every "readystatechange" instead of the last one. A single page application that swaps a rendered form for a new one left the element of the node it removed in "formInstances", and every reopened modal or revisited wizard step added one more. A render that is no longer in the document is now dropped when the same form is initialized again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66Ton99
commented
Aug 27, 2026
Follow-up from review — |
| reverted | fails |
|---|---|
the readyState condition | still waits for the document while its deferred scripts run, turns the native UI off with a configuration a deferred script sets |
| the named handler | leaves no listener behind once the document is ready |
| dropping detached instances | forgets the instance of a render that was taken out of the document |
Docs
2_3.md gains two subsections: one on forms loaded after the page — including that innerHTML does not execute a <script> tag it inserts, so a fragment dropped that way is rendered and never initialized — and one holding the existing onLoad = false example. 3_20.md notes the new getFormInstances() behaviour.
Verification
Full suite in the Nix shell, PHP 8.5.6 / Node 24.16.0:
composer test— 95 tests, 268 assertionscomposer phpstan— no errorsnpm run test:unit— 615 testsnpm run test:coverage— 94.67% line coverage, threshold 80%- Cypress e2e — 24/24
The e2e run earns its place here: jsdom cannot tell interactive from complete, so a real browser is the only place the ordering claim is exercised end to end.
This supersedes the test count and the "PHP checks were not run" note in the description above.
Uh oh!
There was an error while loading. Please reload this page.
Problem
addModel(model, onLoad)defers registration toonDocumentReady(), and that helper only adds aDOMContentLoadedlistener:When a page fetches a rendered form after the initial load and injects it — a single page CRUD, a modal that loads its form, a wizard step — the
addModel()call the fragment carries runs whenDOMContentLoadedhas already been dispatched. The listener is never called, so the form is registered nowhere and keeps no validator at all.The default of
init_js_validation(form)isonLoad = true, so this is the path a fragment hits unless the application knows to passfalse.onLoad = falseis affected too, in a smaller way: it registers the model right away but still defersdisableNativeValidationUi()through the same helper, so withhtml5_validation: truean injected form keeps the native bubble that stops the submit event this library listens to.Change
onDocumentReady()runs the callback immediately oncedocument.readyStateis pastloading, and still waits for the event while the document is being parsed, so the initial page load is unchanged — a model printed inline is still initialized after the document is ready, which is what letsjs_validator_config()appear further down the page.Tests
Two Jest tests in
SvarohJsFormValidator.test.js: a model added while the document is ready registers its form, and a model added whiledocument.readyStateisloadingstill waits for the event. The first fails onmain.npx jest— 610 tests pass (608 before). PHP checks are untouched by this change; the host PHP here is 8.3, below the^8.4requirement, socomposer testwas not run.🤖 Generated with Claude Code