Uh oh!
There was an error while loading. Please reload this page.
feat(js): take a form registration back - #24
Conversation
A page that swaps rendered forms in and out - a single page CRUD, a modal that loads its form - registered a model per render and had no way to remove one. The registry kept every element, including the ones whose markup was already gone, and "getFormInstances" answered with them. "removeModel", "removeForm" and "removeDetachedForms" remove a registration, detach the model from its DOM nodes and take the submit listener off the form. Initializing the same markup again now replaces that listener instead of stacking a second one, which used to run the whole validation twice for one submit.
An element is attached to two nodes, not one: "createElement" attaches it to the node the model id matched, and "initModel" moves the root element to the form it resolved afterwards. The default rendering makes that the common case - "form_start" writes "<form name=...>" without an id and "form_widget" puts the id of the model on a container inside it - and "detachElement" only took the second node back. What that left behind: - the container kept the removed element, and the next initialization of the same markup read it back through "attachElement", which copies the keys of the attached element and repoints "domNode" mid-loop, so the documented "remove, then initialize again" flow threw a TypeError; - "removeForm(document.getElementById(id))", the call the documentation shows, answered with the container while the registry held the form, so it removed nothing; - "removeDetachedForms()" read the root node alone, so a model whose fields are rendered outside of any form tag - "initModel" allows that - counted as detached and was dropped while its widgets were still in the document; - the submit listener went with the first element removed from a form, so a second model rooted in the same form silently stopped being validated. "detachElement" now takes both nodes through "detachNode", which keeps the listener for as long as an element is attached to the node. "removeForm" answers to either node. "removeDetachedForms" asks "isElementInDocument", which looks at the whole element and its children. "attachElement" reads the attached element once instead of re-reading it through a "domNode" it has already overwritten. Also: "keepFormInstances" is "replaceFormInstances", which is what it does; the internal helpers say so in their docblocks; and an id nothing was removed from keeps the array the registry already handed out. Tests: ten more, on the markup Symfony actually renders. 625 pass, JavaScript line coverage 94.96%. PHP is untouched by this change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66Ton99
commented
Aug 27, 2026
ReviewReviewed in a separate worktree, with every finding reproduced by running the code rather than by reading it. Four defects and one regression, all with the same root cause. The seven new tests and both doc examples are built on So
Fixed in e75a67a
Ten more tests, on the markup Symfony renders: attachment to both nodes,
|
Uh oh!
There was an error while loading. Please reload this page.
Problem
addModel()keeps every render informsandformInstances, and nothing takes one back out. A page that swaps rendered forms in and out of the document — a single page CRUD, a modal that loads its form — registers a model per render, so:getFormInstances(id)answers with them;element.domNodeanddomNode.jsFormValidator;initModel()callsattachDefaultEvent()unconditionally, so re-initializing the same markup stacks a secondsubmitlistener on the form and runs the whole validation twice for one submit.Change
Three public methods, each detaching the model from the DOM nodes of the element and its children and removing the submit listener this library put on the form:
removeModel()answers with the number of removed registrations,removeForm()with whether it removed one,removeDetachedForms()with the number it removed. When the last registration of an id is removed, the id leavesformsas well, so the registry never answers with an element it no longer holds.attachDefaultEvent()now keeps its listener on the node and replaces it, so initializing the same markup again validates the form once per submit instead of twice.Nothing changes for a page that never removes a form.
Docs
New
src/Resources/doc/3_24.md, linked from the README list.Tests
Seven Jest tests in
SvarohJsFormValidator.test.jscovering each method, the unknown-id and unknown-node cases, that a removed form no longer runs validation on submit, and that a second initialization of the same markup does not validate twice.npx jest— 615 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