Как предотвратить закрытие модального окна в Orchid Laravel при ошибках валидации и вывести ошибки непосредственно в модальном окне?
Проблема закрытия модального окна Orchid Laravel при ошибках валидации возникает из-за того, что стандартная валидация Laravel вызывает перезагрузку страницы. Чтобы предотвратить это и отображать ошибки непосредственно в модальном окне, необходимо использовать AJAX-запросы для отправки формы и JavaScript для управления состоянием модального окна.
Содержание
- Основная проблема
- Причины возникновения
- Решение с использованием AJAX
- Решение с JavaScript контролем
- Настройка валидации в Orchid
- Полный пример реализации
- Лучшие практики
Основная проблема
Модальные окна в Orchid Laravel закрываются при возникновении ошибок валидации, потому что стандартная отправка формы через HTTP-запрос приводит к перезагрузке страницы. Это создает плохой пользовательский опыт, так как пользователю приходится повторно открывать модальное окно и вводить данные заново.
Проблема связана с тем, что Orchid использует собственный JavaScript контроллер для управления модальными окнами, который при обнаружении ошибок валидации (элементов с классом .invalid-feedback) может блокировать дальнейшую работу с модальными окнами.
Причины возникновения
Основные причины, по которым модальное окно закрывается при ошибках валидации:
-
Стандартная форма отправки: При отправке формы через метод
POSTилиGETпроисходит перезагрузка страницы -
JavaScript контроллер Orchid: Как указано в GitHub issue #2801, контроллер
modal_controller.jsпроверяет наличие элементов с классом.invalid-feedbackи может блокировать работу модальных окон -
Обработка ошибок Laravel: По умолчанию Laravel перенаправляет пользователя обратно на страницу с ошибками в сессии, что приводит к закрытию модального окна
-
Отсутствие AJAX-обработки: Без асинхронной отправки формы страница перезагружается, уничтожая состояние модального окна
Решение с использованием AJAX
Наиболее эффективным решением является использование AJAX для отправки формы без перезагрузки страницы.
Шаг 1: Создание роута для AJAX-обработки
// routes/web.php
Route::post('/modal-submit', [YourController::class, 'modalSubmit'])->name('modal.submit');
Шаг 2: Метод контроллера для обработки AJAX
// app/Http/Controllers/YourController.php
public function modalSubmit(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
// другие правила валидации
]);
// Если валидация прошла успешно
// Ваша логика сохранения данных
return response()->json([
'success' => true,
'message' => 'Данные успешно сохранены'
]);
}
Шаг 3: JavaScript обработка формы
// В вашем Blade шаблоне
<script>
document.addEventListener('DOMContentLoaded', function() {
const modalForm = document.querySelector('#your-modal-form');
if (modalForm) {
modalForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const submitButton = this.querySelector('button[type="submit"]');
// Блокируем кнопку на время отправки
submitButton.disabled = true;
submitButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Сохранение...';
fetch('{{ route("modal.submit") }}', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json'
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Закрываем модальное окно при успешном сохранении
const modal = bootstrap.Modal.getInstance(document.querySelector('#yourModal'));
modal.hide();
// Показываем уведомление об успехе
alert(data.message);
} else {
// Обработка ошибок
if (data.errors) {
displayValidationErrors(data.errors);
}
}
})
.catch(error => {
console.error('Error:', error);
alert('Произошла ошибка при отправке формы');
})
.finally(() => {
// Восстанавливаем кнопку
submitButton.disabled = false;
submitButton.innerHTML = 'Сохранить';
});
});
}
});
// Функция для отображения ошибок валидации
function displayValidationErrors(errors) {
// Очищаем предыдущие ошибки
document.querySelectorAll('.invalid-feedback').forEach(el => el.remove());
document.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
// Отображаем новые ошибки
Object.keys(errors).forEach(field => {
const input = document.querySelector(`[name="${field}"]`);
if (input) {
input.classList.add('is-invalid');
const feedback = document.createElement('div');
feedback.className = 'invalid-feedback';
feedback.textContent = errors[field][0];
input.parentNode.appendChild(feedback);
}
});
}
</script>
Решение с JavaScript контролем
Если вы предпочитаете использовать стандартную валидацию Laravel, можно добавить JavaScript для автоматического открытия модального окна при наличии ошибок.
Шаг 1: Модификация контроллера Orchid
Создайте кастомный JavaScript контроллер для управления модальными окнами:
// resources/js/controllers/modal_controller.js
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
connect() {
this.element.addEventListener('shown.bs.modal', this.onModalShown.bind(this));
}
onModalShown() {
// Проверяем наличие ошибок валидации
const hasErrors = this.element.querySelectorAll('.invalid-feedback').length > 0;
if (hasErrors) {
// Предотвращаем закрытие модального окна при клике на фон
this.element.addEventListener('click', this.handleBackdropClick.bind(this));
// Предотвращаем закрытие по Escape
this.element.addEventListener('keydown', this.handleEscapeKey.bind(this));
}
}
handleBackdropClick(e) {
if (e.target === this.element) {
e.preventDefault();
e.stopPropagation();
}
}
handleEscapeKey(e) {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
}
}
}
Шаг 2: Автоматическое открытие модального окна при ошибках
// В вашем Blade шаблоне
<script>
@if (count($errors) > 0)
document.addEventListener('DOMContentLoaded', function() {
const modal = new bootstrap.Modal(document.querySelector('#yourModal'));
modal.show();
// Добавляем обработчик для предотвращения закрытия
const modalElement = document.querySelector('#yourModal');
modalElement.addEventListener('hide.bs.modal', function(e) {
const hasErrors = modalElement.querySelectorAll('.invalid-feedback').length > 0;
if (hasErrors) {
e.preventDefault();
e.stopPropagation();
}
});
});
@endif
</script>
Настройка валидации в Orchid
Для правильной работы валидации в модальных окнах Orchid необходимо правильно настроить валидацию и отображение ошибок.
Шаг 1: Создание Screen с валидацией
// app/Orchid/Screens/YourScreen.php
namespace Orchid\Screen\Layouts;
use Orchid\Screen\Layout;
use Orchid\Screen\Fields\Input;
class YourModalLayout extends Layout
{
protected $template = 'platform::layouts.modal';
public function fields(): array
{
return [
Input::make('name')
->title('Имя')
->placeholder('Введите имя')
->required(),
Input::make('email')
->title('Email')
->type('email')
->placeholder('user@example.com')
->required(),
];
}
}
Шаг 2: Обработка валидации в методе
// app/Orchid/Screens/YourScreen.php
public function saveData(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
]);
// Логика сохранения
// ...
return redirect()->route('dashboard')
->with('success', 'Данные успешно сохранены');
}
Шаг 3: Blade шаблон модального окна
<!-- resources/views/platform/layouts/modal.blade.php -->
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Заголовок модального окна</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{{ csrf_field() }}
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{{ $template }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</div>
</div>
Полный пример реализации
Вот полный пример реализации модального окна с AJAX-обработкой и валидацией в Orchid:
Шаг 1: Создание Screen
// app/Orchid/Screens/UserScreen.php
namespace Orchid\Screen;
use App\Models\User;
use Illuminate\Http\Request;
use Orchid\Screen\Actions\ModalCommand;
use Orchid\Screen\Fields\Input;
use Orchid\Screen\Layouts\Modal;
use Orchid\Support\Facades\Layout;
class UserScreen extends Screen
{
public $name = 'Пользователи';
public $description = 'Управление пользователями';
public function commandBar(): array
{
return [
ModalCommand::make('Добавить пользователя')
->modal('userModal')
->method('saveUser')
->icon('bs plus-circle'),
];
}
public function layout(): array
{
return [
Layout::modal('userModal', [
Layout::rows([
Input::make('user.name')
->title('Имя')
->placeholder('Введите имя')
->required(),
Input::make('user.email')
->title('Email')
->type('email')
->placeholder('user@example.com')
->required(),
]),
])->title('Добавить пользователя'),
];
}
public function saveUser(Request $request)
{
$validated = $request->validate([
'user.name' => 'required|string|max:255',
'user.email' => 'required|email|unique:users,email',
]);
User::create($validated['user']);
return redirect()->route('platform.main')
->with('success', 'Пользователь успешно создан');
}
}
Шаг 2: JavaScript для AJAX
<!-- В вашем главном шаблоне -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Обработка всех форм в модальных окнах Orchid
document.querySelectorAll('#userModal form, #userModal .modal-content form').forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const submitButton = this.querySelector('button[type="submit"]');
const modalElement = this.closest('.modal');
// Блокируем кнопку
submitButton.disabled = true;
submitButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Сохранение...';
// Определяем URL для отправки
let url;
if (modalElement.id === 'userModal') {
url = '{{ route("platform.systems.users.store") }}';
} else {
url = this.action;
}
fetch(url, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Закрываем модальное окно
const modal = bootstrap.Modal.getInstance(modalElement);
modal.hide();
// Показываем уведомление
showNotification(data.message, 'success');
// Обновляем таблицу или список
location.reload();
} else {
// Показываем ошибки
if (data.errors) {
displayValidationErrors(data.errors, this);
} else {
showNotification(data.message || 'Произошла ошибка', 'error');
}
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Произошла ошибка при отправке формы', 'error');
})
.finally(() => {
submitButton.disabled = false;
submitButton.innerHTML = 'Сохранить';
});
});
});
});
function displayValidationErrors(errors, form) {
// Очищаем предыдущие ошибки
form.querySelectorAll('.invalid-feedback').forEach(el => el.remove());
form.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
// Отображаем новые ошибки
Object.keys(errors).forEach(field => {
// Преобразуем поле в формат Orchid
const orchidField = field.replace('user.', '');
const input = form.querySelector(`[name*="${orchidField}"], [name="${field}"]`);
if (input) {
input.classList.add('is-invalid');
const feedback = document.createElement('div');
feedback.className = 'invalid-feedback';
feedback.textContent = errors[field][0];
input.parentNode.appendChild(feedback);
}
});
}
function showNotification(message, type) {
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger';
const notification = document.createElement('div');
notification.className = `alert ${alertClass} alert-dismissible fade show position-fixed`;
notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
document.body.appendChild(notification);
// Автоматическое закрытие через 5 секунд
setTimeout(() => {
notification.remove();
}, 5000);
}
</script>
Лучшие практики
При работе с модальными окнами и валидацией в Orchid Laravel придерживайтесь следующих рекомендаций:
-
Используйте AJAX для отправки форм: Это prevents перезагрузку страницы и сохраняет состояние модального окна
-
Обрабатывайте ошибки на стороне клиента: Отображайте ошибки валидации непосредственно в модальном окне для лучшего пользовательского опыта
-
Реализуйте индикаторы загрузки: Показывайте пользователю, что форма обрабатывается
-
Добавляйте валидацию на стороне клиента: Предотвращайте отправку форм с неверными данными до отправки на сервер
-
Используйте уведомления: Информируйте пользователей об успешном выполнении или ошибках
-
Обрабатывайте все возможные сценарии: Учитывайте сетевые ошибки, ошибки валидации и другие исключения
-
Следите за производительностью: Оптимизируйте JavaScript код для быстрой работы модальных окон
-
Тестируйте в разных браузерах: Убедитесь, что решение работает корректно во всех браузерах
-
Документируйте код: Описывайте особенности работы с модальными окнами для других разработчиков
-
Используйте последние версии библиотек: Следите за обновлениями Orchid и Bootstrap для использования последних исправлений и возможностей
Источники
-
Modal with validation errors block another modal to show content · GitHub Issue #2801
-
Keeping modal dialog open after validation error laravel - Stack Overflow
-
How to prevent modal from closing when validation error - Laravel 8 + bootstrap - Stack Overflow
Заключение
Предотвращение закрытия модального окна при ошибках валидации в Orchid Laravel требует комплексного подхода, включающего:
- Использование AJAX для асинхронной отправки форм
- Правильную обработку и отображение ошибок валидации
- Настройку JavaScript контроллеров для управления состоянием модальных окон
- Следование лучшим практикам для обеспечения хорошего пользовательского опыта
Основные решения включают:
- Полная AJAX-реализация с обработкой ошибок на клиентской стороне
- Модификация JavaScript контроллеров Orchid для предотвращения закрытия окна при ошибках
- Комбинирование серверной и клиентской валидации для максимальной надежности
Эти подходы позволят создать удобный интерфейс, где пользователи могут исправлять ошибки прямо в модальном окне без необходимости его повторного открытия.