mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-07-07 08:00:15 +02:00
feat: add bank question via modal + dirty modal guard
- Add question in question bank now opens a modal instead of navigating to a full-page form, consistent with the edit modal pattern - Modal closes are blocked by static backdrop once the user has made any change (input, checkbox, drag-reorder, sort, randomize, remove answer) - Dirty state resets when the modal is fully hidden
This commit is contained in:
@@ -45,6 +45,7 @@ export default class extends Controller {
|
|||||||
|
|
||||||
removeItem(event) {
|
removeItem(event) {
|
||||||
event.target.closest('[data-collection-item]').remove();
|
event.target.closest('[data-collection-item]').remove();
|
||||||
|
this._notifyChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
sortAlphabetically() {
|
sortAlphabetically() {
|
||||||
@@ -56,6 +57,7 @@ export default class extends Controller {
|
|||||||
});
|
});
|
||||||
items.forEach(item => this.collectionTarget.appendChild(item));
|
items.forEach(item => this.collectionTarget.appendChild(item));
|
||||||
this._syncOrdering();
|
this._syncOrdering();
|
||||||
|
this._notifyChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
randomize() {
|
randomize() {
|
||||||
@@ -66,6 +68,11 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
items.forEach(item => this.collectionTarget.appendChild(item));
|
items.forEach(item => this.collectionTarget.appendChild(item));
|
||||||
this._syncOrdering();
|
this._syncOrdering();
|
||||||
|
this._notifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
_notifyChange() {
|
||||||
|
this.element.dispatchEvent(new Event('change', {bubbles: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// — drag-and-drop —
|
// — drag-and-drop —
|
||||||
@@ -115,6 +122,7 @@ export default class extends Controller {
|
|||||||
const isBottom = e.clientY > rect.top + rect.height / 2;
|
const isBottom = e.clientY > rect.top + rect.height / 2;
|
||||||
this.collectionTarget.insertBefore(this._dragging, isBottom ? el.nextSibling : el);
|
this.collectionTarget.insertBefore(this._dragging, isBottom ? el.nextSibling : el);
|
||||||
this._syncOrdering();
|
this._syncOrdering();
|
||||||
|
this._notifyChange();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,29 @@ export default class extends Controller {
|
|||||||
const res = await fetch(url, {headers: {'X-Modal-Request': '1'}});
|
const res = await fetch(url, {headers: {'X-Modal-Request': '1'}});
|
||||||
this.modalBodyTarget.innerHTML = await res.text();
|
this.modalBodyTarget.innerHTML = await res.text();
|
||||||
this._moveFooter();
|
this._moveFooter();
|
||||||
|
this._bindDirty();
|
||||||
this._bindForm(url);
|
this._bindForm(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_bindDirty() {
|
||||||
|
const modal = Modal.getOrCreateInstance(this.modalTarget);
|
||||||
|
modal._config.backdrop = true;
|
||||||
|
modal._config.keyboard = true;
|
||||||
|
|
||||||
|
const markDirty = () => {
|
||||||
|
modal._config.backdrop = 'static';
|
||||||
|
modal._config.keyboard = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.modalBodyTarget.addEventListener('input', markDirty, {once: true});
|
||||||
|
this.modalBodyTarget.addEventListener('change', markDirty, {once: true});
|
||||||
|
|
||||||
|
this.modalTarget.addEventListener('hidden.bs.modal', () => {
|
||||||
|
modal._config.backdrop = true;
|
||||||
|
modal._config.keyboard = true;
|
||||||
|
}, {once: true});
|
||||||
|
}
|
||||||
|
|
||||||
_moveFooter() {
|
_moveFooter() {
|
||||||
if (!this.hasModalFooterTarget) return;
|
if (!this.hasModalFooterTarget) return;
|
||||||
const template = this.modalBodyTarget.querySelector('template[data-modal-footer]');
|
const template = this.modalBodyTarget.querySelector('template[data-modal-footer]');
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ class QuestionBankController extends AbstractController
|
|||||||
{
|
{
|
||||||
$bankQuestion = new BankQuestion();
|
$bankQuestion = new BankQuestion();
|
||||||
|
|
||||||
|
$isModalRequest = $request->headers->has('X-Modal-Request');
|
||||||
|
|
||||||
$form = $this->createForm(BankQuestionFormType::class, $bankQuestion, ['season' => $season]);
|
$form = $this->createForm(BankQuestionFormType::class, $bankQuestion, ['season' => $season]);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
@@ -99,14 +101,29 @@ class QuestionBankController extends AbstractController
|
|||||||
|
|
||||||
$this->addFlash(FlashType::Success, $this->translator->trans('Question added to the question bank'));
|
$this->addFlash(FlashType::Success, $this->translator->trans('Question added to the question bank'));
|
||||||
|
|
||||||
|
if ($isModalRequest) {
|
||||||
|
return new Response('', Response::HTTP_NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]);
|
return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('backoffice/question_bank/form.html.twig', [
|
$template = $isModalRequest
|
||||||
|
? 'backoffice/question_bank/_form_body.html.twig'
|
||||||
|
: 'backoffice/question_bank/form.html.twig';
|
||||||
|
|
||||||
|
$response = $this->render($template, [
|
||||||
'season' => $season,
|
'season' => $season,
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
'bankQuestion' => null,
|
'bankQuestion' => null,
|
||||||
|
'isModal' => $isModalRequest,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($form->isSubmitted()) {
|
||||||
|
$response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8 col-12" data-controller="bo--modal-form">
|
<div class="col-md-8 col-12" data-controller="bo--modal-form">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<a class="btn btn-sm btn-outline-primary" href="{{ path('tvdt_backoffice_question_bank_new', {seasonCode: season.seasonCode}) }}">{{ 'Add question'|trans }}</a>
|
<button class="btn btn-sm btn-outline-primary"
|
||||||
|
data-action="click->bo--modal-form#open"
|
||||||
|
data-url="{{ path('tvdt_backoffice_question_bank_new', {seasonCode: season.seasonCode}) }}"
|
||||||
|
data-modal-title="{{ 'Add question'|trans }}">
|
||||||
|
{{ 'Add question'|trans }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center flex-wrap gap-2 mb-3">
|
<div class="d-flex align-items-center flex-wrap gap-2 mb-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user