Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forms: render sections #16357

Merged
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
115 changes: 115 additions & 0 deletions js/form_renderer_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ class GlpiFormRendererController
*/
#target;

/**
* Active section index
* @type {number}
*/
#section_index;

/**
* Total number of sections
* @type {number}
*/
#number_of_sections;

/**
* Create a new GlpiFormRendererController instance for the given target.
* The target must be a valid form.
Expand All @@ -55,6 +67,12 @@ class GlpiFormRendererController
throw new Error("Target must be a valid form");
}

// Init section data
this.#section_index = 0;
this.#number_of_sections = $(this.#target)
.find("[data-glpi-form-renderer-section]")
.length;

// Init event handlers
this.#initEventHandlers();
}
Expand All @@ -70,6 +88,16 @@ class GlpiFormRendererController
$(this.#target)
.find(`[${action_attribute}=submit]`)
.on("click", () => this.#submitForm());

// Next section form action
$(this.#target)
.find(`[${action_attribute}=next-section]`)
.on("click", () => this.#goToNextSection());

// Previous section form action
$(this.#target)
.find(`[${action_attribute}=previous-section]`)
.on("click", () => this.#goToPreviousSection());
}

/**
Expand Down Expand Up @@ -97,4 +125,91 @@ class GlpiFormRendererController
$(document).trigger('glpi-form-renderer-submit-failed', e);
}
}

/**
* Go to the next section of the form.
*/
#goToNextSection() {
// Hide current section
$(this.#target)
.find(`[data-glpi-form-renderer-section=${this.#section_index}]`)
.addClass("d-none");

// Show next section
this.#section_index++;
$(this.#target)
.find(`[data-glpi-form-renderer-section=${this.#section_index}]`)
.removeClass("d-none");

// Update actions visibility
this.#updateActionsVisiblity();
}

/**
* Go to the previous section of the form.
*/
#goToPreviousSection() {
// Hide current section
$(this.#target)
.find(`[data-glpi-form-renderer-section=${this.#section_index}]`)
.addClass("d-none");

// Show preview section
this.#section_index--;
$(this.#target)
.find(`[data-glpi-form-renderer-section=${this.#section_index}]`)
.removeClass("d-none");

// Update actions visibility
this.#updateActionsVisiblity();
}

/**
* Update the visibility of the actions buttons depending on the active
* section of the form.
*/
#updateActionsVisiblity() {
if (this.#section_index == 0) {
// First section, show next button
$(this.#target)
.find("[data-glpi-form-renderer-action=submit]")
.addClass("d-none");

$(this.#target)
.find("[data-glpi-form-renderer-action=next-section]")
.removeClass("d-none");

$(this.#target)
.find("[data-glpi-form-renderer-action=previous-section]")
.addClass("d-none");

} else if (this.#section_index == (this.#number_of_sections - 1)) { // Minus 1 because section_index is 0-based
// Last section, show submit and previous button
$(this.#target)
.find("[data-glpi-form-renderer-action=submit]")
.removeClass("d-none");

$(this.#target)
.find("[data-glpi-form-renderer-action=next-section]")
.addClass("d-none");

$(this.#target)
.find("[data-glpi-form-renderer-action=previous-section]")
.removeClass("d-none");

} else {
// Any middle section, show next and previous button
$(this.#target)
.find("[data-glpi-form-renderer-action=submit]")
.addClass("d-none");

$(this.#target)
.find("[data-glpi-form-renderer-action=next-section]")
.removeClass("d-none");

$(this.#target)
.find("[data-glpi-form-renderer-action=previous-section]")
.removeClass("d-none");
}
}
}
1 change: 0 additions & 1 deletion src/Form/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
use Glpi\Application\View\TemplateRenderer;
use Html;
use Glpi\DBAL\QuerySubQuery;
use Glpi\Form\QuestionType\QuestionTypeShortAnswerText;
use Glpi\Form\QuestionType\QuestionTypesManager;
use Log;

Expand Down
8 changes: 8 additions & 0 deletions src/Form/Renderer/FormRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ final class FormRenderer
*/
public function render(Form $form): string
{
// Note: the "form_renderer_controller" must not be loaded here as this code
// may be called multiple times using AJAX requests, thus trying to load the
// javascript "GlpiFormRendererController" class multiple times and causing an
// error.
// Each pages that call this method through AJAX must instead include the
// JS controller themselves.

// Load template
$twig = TemplateRenderer::getInstance();
return $twig->render('pages/form_renderer.html.twig', [
'form' => $form,
Expand Down
59 changes: 46 additions & 13 deletions templates/pages/form_renderer.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,41 @@
# ---------------------------------------------------------------------
#}

{# TODO: Maybe it will also be possible to render form outside of modal #}
{# In this case, the modal specific structure and classes references could be conditioned with a parameter #}
{# Is this a single or multi sections forms ? #}
{% set is_single_section_form = form.getSections()|length == 1 %}

<form id="forms_form_answers" method="POST" action="{{ path("ajax/form/answer.php") }}">
<div class="modal-header">
<h5 class="modal-title">
{{ form.fields.name }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ _x("button", "Close") }}"></button>
<div class="mt-3">
<h5 class="modal-title mb-3" id="display_form_modal_label">
{{ form.fields.name }}
</h5>

<div class="text-muted">
{{ form.fields.header|safe_html }}
</div>
</div>

<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ __("Close") }}"></button>
cedric-anne marked this conversation as resolved.
Show resolved Hide resolved
</div>

<div class="modal-body">
<div class="text-muted mb-4">
{{ form.fields.header|safe_html }}
</div>

{% for section in form.getSections() %}
{% for section in form.getSections() %}
{# Is this the first section of a form ? #}
{% set is_first_section = loop.index0 == 0 %}

{# Show only the first section of a multi section forms #}
<div
data-glpi-form-renderer-section="{{ loop.index0 }}"
class="{{ not is_single_section_form and not is_first_section ? "d-none" : "" }}"
>
<h2 class="mb-3">{{ section.fields.name }}</h2>

{% for question in section.getQuestions() %}
{# Compute question type #}
{% set question_type = question.getQuestionType() %}

{# Skip unknown types (may be a disabled plugin) #}
{% if question_type is not null %}
<div class="mb-3">
Expand All @@ -64,16 +80,33 @@
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% endfor %}

<input type="hidden" name="forms_id" value="{{ form.fields.id }}">
<input type="hidden" name="forms_id" value="{{ form.fields.id }}">
</div>

<div class="modal-footer">
<button
type="button"
data-glpi-form-renderer-action="previous-section"
class="btn btn-ghost-secondary d-none"
>
{{ __("Back") }}
</button>

<button
type="button"
data-glpi-form-renderer-action="next-section"
class="btn btn-primary {{ is_single_section_form ? "d-none" : "" }}"
>
{{ __("Continue") }}
</button>

<button
type="button"
data-glpi-form-renderer-action="submit"
class="btn btn-primary"
class="btn btn-primary {{ not is_single_section_form ? "d-none" : "" }}"
>
{{ __("Send form") }}
</button>
Expand Down
Loading