Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion assets/js/app/ajax-save.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ $(document).ready(function() {
$('#toastTitle').addClass(['toast-header', typeClass]);
$('#toastNotification').append(notification);
$('#toastType').append(toastType);
$('#toastBody').append(toastMessage);
// Set as text, because validation messages can contain the value that was rejected.
$('#toastBody').text(toastMessage);

$(document).ready(function() {
let toastElList = [].slice.call(document.querySelectorAll('.toast'));
Expand All @@ -59,6 +60,27 @@ $(document).ready(function() {
});
});
}

// Shows the validation errors of a rejected save above the form, the way the classic
// (non-ajax) save shows them, see `templates/content/edit.html.twig`.
function showValidationErrors(errors) {
let container = $('<div id="editcontent-validation-errors"></div>');

errors.forEach(function(error) {
let alert = $('<div class="alert alert-danger" role="alert"></div>');
alert.text(error.property ? error.property + ': ' + error.message : error.message);
container.append(alert);
});

clearValidationErrors();
$(form).before(container);
container[0].scrollIntoView({ block: 'nearest' });
}

function clearValidationErrors() {
$('#editcontent-validation-errors').remove();
}

this.href = window.location.pathname;

let duplicate_id = this.href.substring(this.href.lastIndexOf('/') + 1);
Expand Down Expand Up @@ -86,6 +108,7 @@ $(document).ready(function() {
elementButton.prop('disabled', false);
},
success: function(data, textStatus) {
clearValidationErrors();
if (!record_id) {
window.location.replace(data.url);
} else if (window.location.pathname === '/bolt/duplicate/' + duplicate_id) {
Expand All @@ -99,6 +122,21 @@ $(document).ready(function() {
}
},
error: function(jq, status, err) {
let response = jq.responseJSON;

// The save was rejected by the content validator, which returns its
// violations as JSON, so we can show the validator's own messages.
if (response && response.errors) {
Comment thread
mindaugasjackunaspc marked this conversation as resolved.
Outdated
let messages = response.errors.map(function(error) {
return error.message;
});

showValidationErrors(response.errors);
showToast(response.type, messages.join(' '), response.status, response.notification, dom_element);

return;
}

// eslint-disable-next-line no-console
console.log(status, err);
showToast();
Expand Down
31 changes: 31 additions & 0 deletions src/Controller/Backend/ContentEditController.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

Expand Down Expand Up @@ -153,6 +154,13 @@ public function save(Request $request, ?Content $originalContent = null, ?Conten
if ($enableContentValidator && $contentValidator) {
$constraintViolations = $contentValidator->validate($content);
if (count($constraintViolations) > 0) {
// When "Saving Ajaxy" the editor isn't re-rendered, so the violations that
// renderEditor() puts in the HTML would never reach the browser. Return them
// as JSON instead, for `assets/js/app/ajax-save.js` to display.
if ($request->isXmlHttpRequest()) {
return $this->renderValidationErrors($request, $constraintViolations);
}
Comment thread
mindaugasjackunaspc marked this conversation as resolved.

$this->addFlash('danger', 'content.validation_errors');

return $this->renderEditor($request, $content, $constraintViolations);
Expand Down Expand Up @@ -651,4 +659,27 @@ private function renderEditor(Request $request, Content $content, $errors = null

return $this->render('@bolt/content/edit.html.twig', $twigvars);
}

private function renderValidationErrors(Request $request, ConstraintViolationListInterface $constraintViolations): JsonResponse
{
$locale = $request->getLocale();

$errors = [];
foreach ($constraintViolations as $constraintViolation) {
$errors[] = [
'property' => $constraintViolation->getPropertyPath(),
'message' => (string) $constraintViolation->getMessage(),
];
}

return new JsonResponse(
[
'status' => 'danger',
'type' => $this->translator->trans('warning', [], null, $locale),
'notification' => $this->translator->trans('flash_messages.notification', [], null, $locale),
'errors' => $errors,
],
Response::HTTP_UNPROCESSABLE_ENTITY
);
}
}
Loading