mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-07-07 16:10:15 +02:00
feat: replace fetch-based modal forms with Turbo Frames (#181)
Enable @hotwired/turbo with Drive explicitly disabled, then migrate the bo--modal-form Stimulus controller (custom fetch + X-Modal-Request pattern) to a thin bo--modal controller that lets Turbo handle HTTP and DOM swap. Adds frame templates for quiz questions and bank questions; controllers now detect Turbo-Frame header instead of X-Modal-Request.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
{% import 'backoffice/partials/answer_row.html.twig' as macros %}
|
||||
|
||||
<turbo-frame id="bank-question-modal-frame">
|
||||
{{ form_start(form, {attr: {novalidate: 'novalidate'}}) }}
|
||||
<div class="modal-body">
|
||||
{{ form_row(form.question) }}
|
||||
{{ form_row(form.reusable) }}
|
||||
{{ form_row(form.labels) }}
|
||||
<div data-controller="bo--form-collection"
|
||||
data-bo--form-collection-prototype-value="{{ macros.answer_row(form.answers.vars.prototype)|e('html_attr') }}">
|
||||
{{ form_label(form.answers) }}
|
||||
{{ form_errors(form.answers) }}
|
||||
<div data-bo--form-collection-target="collection">
|
||||
{% for answerForm in form.answers %}
|
||||
{{ macros.answer_row(answerForm) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% do form.answers.setRendered %}
|
||||
<div class="d-flex gap-2 mb-3">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-action="bo--form-collection#addItem">{{ 'Add answer'|trans }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||
data-action="bo--form-collection#sortAlphabetically">{{ 'Sort A–Z'|trans }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||
data-action="bo--form-collection#randomize">{{ 'Randomize'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'Cancel'|trans }}</button>
|
||||
{{ form_widget(form.save, {attr: {class: 'btn btn-primary'}}) }}
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</turbo-frame>
|
||||
@@ -0,0 +1,23 @@
|
||||
<turbo-frame id="question-modal-frame">
|
||||
<div class="modal-body">
|
||||
<p class="fw-semibold mb-3">{{ question.question }}</p>
|
||||
<ul class="list-group">
|
||||
{% for answer in question.answers %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span{% if answer.isRightAnswer %} class="fw-semibold"{% endif %}>
|
||||
{% if answer.isRightAnswer %}<i class="bi bi-check-circle-fill text-success me-1"></i>{% endif %}
|
||||
{{ answer.text }}
|
||||
</span>
|
||||
{% if answer.candidates|length > 0 %}
|
||||
<span class="text-muted small">{{ answer.candidates|map(c => c.name)|join(', ') }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="list-group-item text-muted">{{ 'There are no answers for this question'|trans }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'Close'|trans }}</button>
|
||||
</div>
|
||||
</turbo-frame>
|
||||
@@ -0,0 +1,32 @@
|
||||
{% import 'backoffice/partials/answer_row.html.twig' as macros %}
|
||||
|
||||
<turbo-frame id="question-modal-frame">
|
||||
{{ form_start(form, {attr: {novalidate: 'novalidate'}}) }}
|
||||
<div class="modal-body">
|
||||
{{ form_row(form.question) }}
|
||||
<div data-controller="bo--form-collection"
|
||||
data-bo--form-collection-prototype-value="{{ macros.answer_row(form.answers.vars.prototype)|e('html_attr') }}">
|
||||
{{ form_label(form.answers) }}
|
||||
{{ form_errors(form.answers) }}
|
||||
<div data-bo--form-collection-target="collection">
|
||||
{% for answerForm in form.answers %}
|
||||
{{ macros.answer_row(answerForm) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% do form.answers.setRendered %}
|
||||
<div class="d-flex gap-2 mb-3">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-action="bo--form-collection#addItem">{{ 'Add answer'|trans }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||
data-action="bo--form-collection#sortAlphabetically">{{ 'Sort A–Z'|trans }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||
data-action="bo--form-collection#randomize">{{ 'Randomize'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'Cancel'|trans }}</button>
|
||||
{{ form_widget(form.save, {attr: {class: 'btn btn-primary'}}) }}
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</turbo-frame>
|
||||
@@ -85,7 +85,8 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div data-controller="bo--question-list bo--modal-form"
|
||||
<div data-controller="bo--question-list bo--modal"
|
||||
data-action="turbo:frame-load->bo--modal#frameLoad turbo:submit-end->bo--modal#frameSubmitEnd"
|
||||
data-bo--question-list-reorder-url-value="{{ path('tvdt_backoffice_quiz_questions_reorder', {seasonCode: season.seasonCode, quiz: quiz.id}) }}"
|
||||
data-bo--question-list-csrf-value="{{ csrf_token('question_reorder') }}"
|
||||
data-bo--question-list-can-modify-value="{{ is_granted('QUIZ_MODIFY_CONTENT', quiz) ? 'true' : 'false' }}"
|
||||
@@ -117,15 +118,15 @@
|
||||
</div>
|
||||
{% if is_granted('QUIZ_MODIFY_CONTENT', question) %}
|
||||
<button class="btn btn-sm btn-outline-secondary flex-shrink-0"
|
||||
data-action="click->bo--modal-form#open"
|
||||
data-url="{{ path('tvdt_backoffice_quiz_question_edit', {seasonCode: season.seasonCode, quiz: quiz.id, question: question.id}) }}"
|
||||
data-action="click->bo--modal#open"
|
||||
data-src="{{ path('tvdt_backoffice_quiz_question_edit', {seasonCode: season.seasonCode, quiz: quiz.id, question: question.id}) }}"
|
||||
data-modal-title="{{ 'Edit question'|trans }}">
|
||||
<i class="bi bi-pencil"></i> {{ 'Edit'|trans }}
|
||||
</button>
|
||||
{% elseif quiz.isLocked %}
|
||||
<button class="btn btn-sm btn-outline-secondary flex-shrink-0"
|
||||
data-action="click->bo--modal-form#open"
|
||||
data-url="{{ path('tvdt_backoffice_quiz_question_view', {seasonCode: season.seasonCode, quiz: quiz.id, question: question.id}) }}"
|
||||
data-action="click->bo--modal#open"
|
||||
data-src="{{ path('tvdt_backoffice_quiz_question_view', {seasonCode: season.seasonCode, quiz: quiz.id, question: question.id}) }}"
|
||||
data-modal-title="{{ 'Question details'|trans }}">
|
||||
<i class="bi bi-eye"></i> {{ 'View'|trans }}
|
||||
</button>
|
||||
@@ -139,7 +140,7 @@
|
||||
</div>
|
||||
|
||||
<div class="modal fade" tabindex="-1"
|
||||
data-bo--modal-form-target="modal"
|
||||
data-bo--modal-target="modal"
|
||||
aria-labelledby="questionEditModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
@@ -147,8 +148,7 @@
|
||||
<h5 class="modal-title" id="questionEditModalLabel">{{ 'Edit question'|trans }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" data-bo--modal-form-target="modalBody"></div>
|
||||
<div class="modal-footer" data-bo--modal-form-target="modalFooter"></div>
|
||||
<turbo-frame id="question-modal-frame" data-bo--modal-target="frame"></turbo-frame>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<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"
|
||||
data-action="turbo:frame-load->bo--modal#frameLoad turbo:submit-end->bo--modal#frameSubmitEnd">
|
||||
<div class="mb-3">
|
||||
<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-action="click->bo--modal#open"
|
||||
data-src="{{ path('tvdt_backoffice_question_bank_new', {seasonCode: season.seasonCode}) }}"
|
||||
data-modal-title="{{ 'Add question'|trans }}">
|
||||
{{ 'Add question'|trans }}
|
||||
</button>
|
||||
@@ -126,8 +127,8 @@
|
||||
{% endif %}
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-action="click->bo--modal-form#open"
|
||||
data-url="{{ path('tvdt_backoffice_question_bank_edit', {seasonCode: season.seasonCode, bankQuestion: bankQuestion.id}) }}"
|
||||
data-action="click->bo--modal#open"
|
||||
data-src="{{ path('tvdt_backoffice_question_bank_edit', {seasonCode: season.seasonCode, bankQuestion: bankQuestion.id}) }}"
|
||||
title="{{ 'Edit'|trans }}"><i class="bi bi-pencil"></i></button>
|
||||
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal"
|
||||
data-bs-target="#deleteBankQuestion-{{ bankQuestion.id }}"
|
||||
@@ -170,7 +171,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="modal fade" tabindex="-1"
|
||||
data-bo--modal-form-target="modal"
|
||||
data-bo--modal-target="modal"
|
||||
aria-labelledby="bankQuestionEditModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
@@ -178,8 +179,7 @@
|
||||
<h5 class="modal-title" id="bankQuestionEditModalLabel">{{ 'Edit question'|trans }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" data-bo--modal-form-target="modalBody"></div>
|
||||
<div class="modal-footer" data-bo--modal-form-target="modalFooter"></div>
|
||||
<turbo-frame id="bank-question-modal-frame" data-bo--modal-target="frame"></turbo-frame>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<a class="btn btn-sm btn-outline-primary"
|
||||
href="{{ path('tvdt_backoffice_quiz_add', {seasonCode: season.seasonCode}) }}">{{ 'Import Quiz from Excel'|trans }}</a>
|
||||
<a class="btn btn-sm btn-outline-secondary"
|
||||
href="{{ path('tvdt_backoffice_quiz_add_blank', {seasonCode: season.seasonCode}) }}">{{ 'Add Empty Quiz'|trans }}</a>
|
||||
<a class="btn btn-sm btn-outline-secondary"
|
||||
href="{{ path('tvdt_backoffice_quiz_add', {seasonCode: season.seasonCode}) }}">{{ 'Import Quiz from Excel'|trans }}</a>
|
||||
</div>
|
||||
<div class="list-group mb-3">
|
||||
{% for quiz in season.quizzes %}
|
||||
|
||||
Reference in New Issue
Block a user