Show content validation errors when Saving Ajaxy - #3769
Show content validation errors when Saving Ajaxy#3769mindaugasjackunaspc wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
🟡 Not ready to approve
The AJAX error handler should gate the validation-error rendering on the expected 422 + array payload to avoid misclassifying unrelated JSON error responses.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR fixes “ajaxy saving” validation UX by returning content-validator violations as JSON on XHR saves (instead of HTML) and teaching the admin AJAX save handler to render those violations inline and in the toast, while preventing HTML injection in toast bodies.
Changes:
- Return validation violations as
422 Unprocessable EntityJSON whenContentEditController::save()is called via XHR. - Render validator violations above the edit form on AJAX save failure, and clear them on the next successful save.
- Use
.text()(not.append()) for toast body to avoid interpreting validator messages as HTML.
File summaries
| File | Description |
|---|---|
| src/Controller/Backend/ContentEditController.php | Adds an XHR-only JSON response path for content-validator violations (422) while keeping classic POST behavior unchanged. |
| assets/js/app/ajax-save.js | Displays validation errors above the form + shows validator messages in toast; switches toast body rendering to .text(). |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
What
With
ajaxy_saving: true, saving an existing record posts the edit form throughassets/js/app/ajax-save.js, which expects a JSON response. When the content validator rejects the save,ContentEditController::save()returns the re-rendered editor (HTML) instead. The handler cannot read that: jQuery treats the 200 as a success,data.type/data.messageareundefined, and the editor only gets the fallback toast "Error — Failed trying to save!". The validator's messages never reach the browser, so the editor is told the save failed but not why.This PR:
422 Unprocessable Entity) when the save came in over ajax, leaving the classic POST path untouched;alert alert-dangerblocks above the form from the ajax handler — same markup and position as the{% if errors is defined %}block intemplates/content/edit.html.twig— and shows a toast with the validator's messages instead of the generic one;.text()instead of.append(), because Symfony violation messages can contain the rejected value and must not be parsed as HTML.showToast()is only called from this file and every caller passes plain text.Response payload:
{ "status": "danger", "type": "Warning", "notification": "Notification", "errors": [{ "property": "end_datetime", "message": "End datetime is required" }] }Why
ajaxy_saving(#3253) made the save button post over ajax, but the validation branch ofsave()still answers with HTML. The effect is that enabling the flag hides all content-validation feedback: customContentValidatorInterfaceimplementations (withvalidator_options.enable: true) still block the save, but the editor only ever sees "Failed trying to save!" and has no way to find out which field is wrong. Turningajaxy_savingoff is not a real workaround, since that also drops the unsaved-changes warning that comes with it.No new translation keys are introduced:
warningandflash_messages.notificationalready exist, and the individual messages come from the validators themselves.How to test
config/bolt/config.yamlsetajaxy_saving: trueandvalidator_options: { enable: true }.Bolt\Validator\ContentValidatorInterfacethat rejects a record — e.g. return one violation when a given field is empty./bolt/edit/<id>), make it violate that rule and press Save:property: message), and the toast carries the validator's messages.ajaxy_saving: falseand repeat step 3 → unchanged classic behaviour: the page reloads and the same messages render fromedit.html.twig.Notes
bolt/assets, so the JS half of this fix only reaches projects once that package is re-tagged alongside abolt/corerelease.tests/php/Controller/Backend/ContentEditControllerTest.bakis disabled (those functional tests need the frontend assets to be built) and the repo has no JS tests yet. Happy to add coverage if you can point me at the harness you'd prefer.content.validation_errors, used by theaddFlash()on the classic path, has no entry intranslations/messages.en.xlf, so that flash renders as the raw key. Left alone to keep this PR focused.