feat(js): dispatch validation lifecycle events - #17
Merged
Merged
Conversation
showErrors is called both when previous errors are cleared and when new ones are rendered, so it cannot tell a started validation run from a finished one, and it marks a field as valid while an asynchronous constraint is still waiting for its answer. Dispatch native CustomEvent objects instead: svaroh:validating, svaroh:success and svaroh:failure on the element DOM node, and svaroh:form-validating, svaroh:form-success and svaroh:form-failure on the form DOM node. The success and failure events wait for the ajax queue to drain so they describe the final result of the run. Queued ajax callbacks are now taken off the queue before they run, they were kept and replayed on every following drain. Fixes formapro#60
66Ton99
force-pushed
the
feat/issue-60-validation-events
branch
from
August 24, 2026 19:31
398114f to
b6266f5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was broken
formapro/JsFormValidatorBundle#60 asks for three validation events — validating, success, failure — because the only extension point today is
showErrors, and it is called both before and after validation. The reporter also saw every field get areadyclass regardless of whether its check had finished.Root cause
showErrorsreports an error list, not a lifecycle state.SvarohJsFormElement.validate()starts withclearErrorsRecursively(sourceId), which callsshowErrors([], sourceId)on the element and every descendant, then callsshowErrorsagain with the real errors. An empty list therefore means either "cleared, not checked yet" or "checked, valid".UniqueEntityreturns[]fromvalidate()and reports its errors later, from the ajax response. So a field covered by aUniqueEntitycheck is told "no errors" twice before the server has answered.The
readyclass the reporter mentions is not a second defect, it is this one: with the example in doc 3.12, theshowErrors([])of the clearing pass addsreadyto fields that have not been validated yet and to fields whose uniqueness request is still in flight. No separate fix is possible without changing whatshowErrors([])means, which would break existing user code, so it is fixed by giving the lifecycle its own signal.One real second bug was found and fixed on the way:
SvarohJsAjaxRequest.checkQueue()iteratedthis.callbackswithout clearing it, so every queued callback was replayed on each following drain — a submit deferred behind the ajax queue re-ran on the next uniqueness check.What changed
src/Resources/public/js/SvarohJsFormValidator.js, additive only.showErrorsandonValidateare untouched and existing customizations keep working.Element level, dispatched on the field DOM node, bubbling:
svaroh:validating—validate()starts on this element (disabled elements are skipped and fire nothing)svaroh:success/svaroh:failure— the element ended without / with errorsForm level, dispatched on the form DOM node, once per
validateRecursively()on a root element, which is what a submit triggers:svaroh:form-validating,svaroh:form-success,svaroh:form-failuredetail.elementis theSvarohJsFormElement.detail.errorsis a flat message list for element events (same shape as theshowErrorsargument) and an object keyed by element id for form events (same shape as theonValidateargument).SvarohJsFormValidator.eventPrefixmakes thesvaroh:prefix configurable.Async:
success,failure,form-successandform-failurego through the newSvarohJsFormValidator.onAjaxIdle(), which fires straight away whenajax.queueis 0 and otherwise queues onajax.callbacks, so the outcome is reported only after theUniqueEntityresponses have landed and written their errors. Element outcome is computed from all of the element's error sources at that moment, not from the synchronous return value.Native CustomEvent rather than callbacks
Chosen deliberately. The core
SvarohJsFormValidator.jshas no jQuery dependency, and jQuery's.on()receives events dispatched withdispatchEventand copiesdetailonto its own event object, so the same three names work in all three entry points (SvarohJsFormValidator.js,SvarohJsFormValidatorWithJqueryInit.js,jquery.svarohjsformvalidator.js) with no bridge code injquery.svarohjsformvalidator.jsat all. Callbacks would have meant a single-slot-per-element property likeshowErrors, where a second listener silently replaces the first — the opposite of what an event API is for. Events also bubble, so$('form').on('svaroh:failure', 'input', ...)works, which matters here because the bundle attaches exactly one listener of its own (submiton the form) and has no field-level change pipeline. Form level events use distinct names precisely because element events bubble up to the form and would otherwise be indistinguishable there.Docs
New page
src/Resources/doc/3_18.mdwith the event table, the twodetailshapes, jQuery and plain JS examples, the async section, and a corrected version of the marker-class example. Pointers added from 3.2, 3.11 and 3.12, plus the README index.3_15,3_16and3_17are claimed by three sibling branches, so this page took the next free number; renumbering at merge time is expected.Tests
nix develop -c npx jest: 34 suites, 357 tests (was 347), all green.composer test: 43 tests / 151 assertions.composer phpstan: clean.10 tests added in
SvarohJsFormValidator.test.js; 8 of them fail onmainwithout this change. They cover element level ordering and payloads, disabled elements, bubbling, form level events on a valid and on an invalid form, no form level events for a child element, the one-shot ajax callback queue, and the async path: a realUniqueEntityconstraint with a stubbed XHR asserting that onlyvalidatingandform-validatinghave fired while the request is pending, and thatfailureandform-failurearrive with the uniqueness message once the response lands.Known limitation, not addressed
sendRequestonly reacts toreadyState === 4 && status === 200. A failed uniqueness request leavesajax.queuestuck above 0, so the queued callbacks never run. That already blocked the deferred submit before this change; the new events inherit the same limitation and it belongs to its own issue.