Skip to content

Show content validation errors when Saving Ajaxy - #3769

Open
mindaugasjackunaspc wants to merge 5 commits into
bolt:6.1from
mindaugasjackunaspc:fix/validation-errors-on-ajaxy-save
Open

Show content validation errors when Saving Ajaxy#3769
mindaugasjackunaspc wants to merge 5 commits into
bolt:6.1from
mindaugasjackunaspc:fix/validation-errors-on-ajaxy-save

Conversation

@mindaugasjackunaspc

Copy link
Copy Markdown

What

With ajaxy_saving: true, saving an existing record posts the edit form through assets/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.message are undefined, 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:

  • returns the violations as JSON (422 Unprocessable Entity) when the save came in over ajax, leaving the classic POST path untouched;
  • renders them as alert alert-danger blocks above the form from the ajax handler — same markup and position as the {% if errors is defined %} block in templates/content/edit.html.twig — and shows a toast with the validator's messages instead of the generic one;
  • clears those alerts on the next successful save;
  • sets the toast body with .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 of save() still answers with HTML. The effect is that enabling the flag hides all content-validation feedback: custom ContentValidatorInterface implementations (with validator_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. Turning ajaxy_saving off is not a real workaround, since that also drops the unsaved-changes warning that comes with it.

No new translation keys are introduced: warning and flash_messages.notification already exist, and the individual messages come from the validators themselves.

How to test

  1. In config/bolt/config.yaml set ajaxy_saving: true and validator_options: { enable: true }.
  2. Register a service implementing Bolt\Validator\ContentValidatorInterface that rejects a record — e.g. return one violation when a given field is empty.
  3. Edit an existing record (/bolt/edit/<id>), make it violate that rule and press Save:
    • before this PR: toast "Error — Failed trying to save!", no further detail;
    • after: one red alert per violation above the form (property: message), and the toast carries the validator's messages.
  4. Fix the content and press Save again → success toast, the alerts disappear, no page reload.
  5. Set ajaxy_saving: false and repeat step 3 → unchanged classic behaviour: the page reloads and the same messages render from edit.html.twig.

Notes

  • The compiled admin bundle ships from bolt/assets, so the JS half of this fix only reaches projects once that package is re-tagged alongside a bolt/core release.
  • No automated test: tests/php/Controller/Backend/ContentEditControllerTest.bak is 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.
  • Unrelated but noticed while working here: content.validation_errors, used by the addFlash() on the classic path, has no entry in translations/messages.en.xlf, so that flash renders as the raw key. Left alone to keep this PR focused.

Copilot AI review requested due to automatic review settings August 3, 2026 14:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 Entity JSON when ContentEditController::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.

Comment thread assets/js/app/ajax-save.js Outdated
Comment thread src/Controller/Backend/ContentEditController.php
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants