diff --git a/js/form_renderer_controller.js b/js/form_renderer_controller.js new file mode 100644 index 00000000000..aea07c40787 --- /dev/null +++ b/js/form_renderer_controller.js @@ -0,0 +1,223 @@ +/** + * --------------------------------------------------------------------- + * + * GLPI - Gestionnaire Libre de Parc Informatique + * + * http://glpi-project.org + * + * @copyright 2015-2023 Teclib' and contributors. + * @copyright 2003-2014 by the INDEPNET Development Team. + * @licence https://www.gnu.org/licenses/gpl-3.0.html + * + * --------------------------------------------------------------------- + * + * LICENSE + * + * This file is part of GLPI. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * --------------------------------------------------------------------- + */ + +/** + * Client code to handle users actions on the form_renderer template + */ +class GlpiFormRendererController +{ + /** + * Target form (jquery selector) + * @type {string} + */ + #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. + * + * @param {string} target + */ + constructor(target) { + // Target must be a valid form + this.#target = target; + if ((this.#target).prop("tagName") != "FORM") { + console.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(); + } + + /** + * Init event handlers for each possible actions, identified by the data + * attribute "data-glpi-form-renderer-action". + * + * The available actions are: + * - "submit": submit the form + * - "next-section": go to the next section + * - "previous-section": go to the previous section + * + * () => fn() is used for most actions to keep the context of the + * GlpiFormRendererController instance + */ + #initEventHandlers() { + const action_attribute = "data-glpi-form-renderer-action"; + + // Submit form action + $(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()); + } + + /** + * Submit the target form using an AJAX request. + * + * The event "glpi-form-renderer-submit-success" is triggered on success, + * with the response as argument. + * + * The event "glpi-form-renderer-submit-failed" is triggered on failure, + * with the response as argument. + */ + async #submitForm() { + // Form will be sumitted using an AJAX request instead + try { + // Submit form using AJAX + const response = await $.post({ + url: $(this.#target).prop("action"), + data: $(this.#target).serialize(), + }); + + // Success event + $(document).trigger('glpi-form-renderer-submit-success', response); + } catch (e) { + // Failure event + $(document).trigger('glpi-form-renderer-submit-failed', response); + } + } + + /** + * 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"); + } + } +} diff --git a/src/Form/Form.php b/src/Form/Form.php index 71835770c59..513619c8413 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -41,6 +41,7 @@ use Glpi\DBAL\QuerySubQuery; use Glpi\Form\QuestionType\QuestionTypeShortAnswerText; use Glpi\Form\QuestionType\QuestionTypesLoader; +use Html; use Log; /** @@ -94,6 +95,9 @@ public function showForm($id, array $options = []) } $this->initForm($id, $options); + // We will be previewing forms from this page + echo Html::script("js/form_renderer_controller.js"); + // Render twig template $twig = TemplateRenderer::getInstance(); $twig->display('pages/admin/form/form_editor.html.twig', [ diff --git a/src/Form/Renderer/FormRenderer.php b/src/Form/Renderer/FormRenderer.php index 968cc661255..03b43ff5983 100644 --- a/src/Form/Renderer/FormRenderer.php +++ b/src/Form/Renderer/FormRenderer.php @@ -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/admin/form/render_form.html.twig', [ 'form' => $form, diff --git a/templates/pages/admin/form/form_editor.html.twig b/templates/pages/admin/form/form_editor.html.twig index e0fc6f123c4..320c723d05a 100644 --- a/templates/pages/admin/form/form_editor.html.twig +++ b/templates/pages/admin/form/form_editor.html.twig @@ -444,7 +444,7 @@ }); // Handle preview successful submit - $(document).on('forms-form-submit-success', function(e, data) { + $(document).on('glpi-form-renderer-submit-success', function(e, data) { // Close modal $("#preview_form_modal").modal('hide'); diff --git a/templates/pages/admin/form/render_form.html.twig b/templates/pages/admin/form/render_form.html.twig index e1663627f81..8e9f5c1c5e7 100644 --- a/templates/pages/admin/form/render_form.html.twig +++ b/templates/pages/admin/form/render_form.html.twig @@ -31,25 +31,43 @@ # --------------------------------------------------------------------- #} -{# 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 %}