From 4f266bd724a351b2b9f9be7d47e1646be3d13c62 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Mon, 9 Dec 2024 23:34:32 -0500 Subject: [PATCH 1/4] 283 Initial cancel modal for eval form --- app/javascript/confirmation_modal.js | 33 ++++++++++ app/views/evaluation_forms/_form.html.erb | 15 ++++- app/views/layouts/application.html.erb | 1 + app/views/modals/_confirmation.erb | 32 ++++++++++ spec/system/evaluation_form_spec.rb | 74 +++++++++++++++++++++++ 5 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 app/javascript/confirmation_modal.js create mode 100644 app/views/modals/_confirmation.erb diff --git a/app/javascript/confirmation_modal.js b/app/javascript/confirmation_modal.js new file mode 100644 index 00000000..4bb6b4fb --- /dev/null +++ b/app/javascript/confirmation_modal.js @@ -0,0 +1,33 @@ +document.addEventListener("DOMContentLoaded", () => { + const modalTriggers = document.querySelectorAll("[data-modal]"); + + modalTriggers.forEach((trigger) => { + trigger.addEventListener("click", (e) => { + console.log(e); + e.preventDefault(); + + modalId = trigger.dataset.modal; + modal = document.getElementById(modalId); + + if (modal) { + const confirmButton = modal.querySelector("#modal-btn-confirm"); + if (confirmButton) { + confirmButton.onclick = () => { + modal.close(); + return true; + }; + } + + const cancelButton = modal.querySelector("#modal-btn-cancel"); + if (cancelButton) { + cancelButton.onclick = () => { + modal.close(); + return false; + }; + } + + modal.showModal(); + } + }); + }); +}); diff --git a/app/views/evaluation_forms/_form.html.erb b/app/views/evaluation_forms/_form.html.erb index e717647b..87c6aeff 100644 --- a/app/views/evaluation_forms/_form.html.erb +++ b/app/views/evaluation_forms/_form.html.erb @@ -198,7 +198,16 @@ Save
- <%= link_to "Cancel", evaluation_forms_path %> -
+ <%= link_to "Cancel", evaluation_forms_path, data: {"modal": "cancel-modal"} %> + -<% end %> \ No newline at end of file +<% end %> + +<%= render "modals/confirmation", + id: "cancel-modal", + heading: "Are you sure you want to cancel?", + description: "Your evaluation form will not be saved and any entered information will be lost.", + confirm_text: "Yes", + confirm_redirect: evaluation_forms_path, + cancel_text: "Close" +%> \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 82cc5812..94294012 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -18,6 +18,7 @@ <%= javascript_include_tag 'session_timeout' %> + <%= javascript_include_tag 'confirmation_modal' %> <% end %> diff --git a/app/views/modals/_confirmation.erb b/app/views/modals/_confirmation.erb new file mode 100644 index 00000000..285228be --- /dev/null +++ b/app/views/modals/_confirmation.erb @@ -0,0 +1,32 @@ + + aria-describedby=<%= "#{id}-description" %> +> +
+
+

> + <%= heading %> +

+
+

> + <%= description %> +

+
+ +
+
+
\ No newline at end of file diff --git a/spec/system/evaluation_form_spec.rb b/spec/system/evaluation_form_spec.rb index e386e5e5..2d0ae375 100644 --- a/spec/system/evaluation_form_spec.rb +++ b/spec/system/evaluation_form_spec.rb @@ -16,6 +16,43 @@ expect(page).to(be_axe_clean) end + it "shows a confirmation modal when clicking the cancel button" do + visit new_evaluation_form_path + + click_link_or_button "Cancel" + + assert_selector 'dialog#cancel-modal', visible: true + end + + it "redirects to evaluation form path when clicking yes in cancel modal" do + visit new_evaluation_form_path + + click_link_or_button "Cancel" + + assert_selector 'dialog#cancel-modal', visible: true + + within 'dialog#cancel-modal' do + click_link_or_button 'Yes' + end + + assert_current_path evaluation_forms_path + end + + it "closes the cancel modal and does nothing if you click close" do + visit new_evaluation_form_path + + click_link_or_button "Cancel" + + assert_selector 'dialog#cancel-modal', visible: true + + within 'dialog#cancel-modal' do + click_link_or_button 'Close' + end + + assert_no_selector 'dialog#cancel-modal', visible: true + assert_current_path new_evaluation_form_path + end + it 'allows creation of a valid form with all 3 criteria scoring types' do visit new_evaluation_form_path @@ -203,6 +240,43 @@ expect(page).to(be_axe_clean) end + it "shows a confirmation modal when clicking the cancel button" do + visit edit_evaluation_form_path(evaluation_form) + + click_link_or_button "Cancel" + + assert_selector 'dialog#cancel-modal', visible: true + end + + it "redirects to evaluation form path when clicking yes in cancel modal" do + visit edit_evaluation_form_path(evaluation_form) + + click_link_or_button "Cancel" + + assert_selector 'dialog#cancel-modal', visible: true + + within 'dialog#cancel-modal' do + click_link_or_button 'Yes' + end + + assert_current_path evaluation_forms_path + end + + it "closes the cancel modal and does nothing if you click close" do + visit edit_evaluation_form_path(evaluation_form) + + click_link_or_button "Cancel" + + assert_selector 'dialog#cancel-modal', visible: true + + within 'dialog#cancel-modal' do + click_link_or_button 'Close' + end + + assert_no_selector 'dialog#cancel-modal', visible: true + assert_current_path edit_evaluation_form_path(evaluation_form) + end + it 'allows editing of an existing form values' do visit edit_evaluation_form_path(evaluation_form) From 5e6d03a6f12bf9160d3658f83a94b14dc5812406 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Mon, 9 Dec 2024 23:48:33 -0500 Subject: [PATCH 2/4] 283 Add accessibility test for cancel modal --- spec/system/evaluation_form_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/system/evaluation_form_spec.rb b/spec/system/evaluation_form_spec.rb index 05da940c..1cefe84e 100644 --- a/spec/system/evaluation_form_spec.rb +++ b/spec/system/evaluation_form_spec.rb @@ -22,6 +22,8 @@ click_link_or_button "Cancel" assert_selector 'dialog#cancel-modal', visible: true + + expect(page).to(be_axe_clean) end it "redirects to evaluation form path when clicking yes in cancel modal" do @@ -304,6 +306,8 @@ click_link_or_button "Cancel" assert_selector 'dialog#cancel-modal', visible: true + + expect(page).to(be_axe_clean) end it "redirects to evaluation form path when clicking yes in cancel modal" do From 65cf200d5f988ecb0077315177734c8b48d7dfb9 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Tue, 10 Dec 2024 22:40:30 -0500 Subject: [PATCH 3/4] 283 Change confirm/cancel modal to stimulus --- app/javascript/confirmation_modal.js | 33 ----------- app/javascript/controllers/index.js | 10 +++- .../controllers/modal_controller.js | 55 +++++++++++++++++++ app/views/evaluation_forms/_form.html.erb | 22 ++++---- app/views/layouts/application.html.erb | 1 - app/views/modals/_confirmation.erb | 16 ++---- 6 files changed, 79 insertions(+), 58 deletions(-) delete mode 100644 app/javascript/confirmation_modal.js create mode 100644 app/javascript/controllers/modal_controller.js diff --git a/app/javascript/confirmation_modal.js b/app/javascript/confirmation_modal.js deleted file mode 100644 index 4bb6b4fb..00000000 --- a/app/javascript/confirmation_modal.js +++ /dev/null @@ -1,33 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const modalTriggers = document.querySelectorAll("[data-modal]"); - - modalTriggers.forEach((trigger) => { - trigger.addEventListener("click", (e) => { - console.log(e); - e.preventDefault(); - - modalId = trigger.dataset.modal; - modal = document.getElementById(modalId); - - if (modal) { - const confirmButton = modal.querySelector("#modal-btn-confirm"); - if (confirmButton) { - confirmButton.onclick = () => { - modal.close(); - return true; - }; - } - - const cancelButton = modal.querySelector("#modal-btn-cancel"); - if (cancelButton) { - cancelButton.onclick = () => { - modal.close(); - return false; - }; - } - - modal.showModal(); - } - }); - }); -}); diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 82491138..dd866ed6 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -8,7 +8,10 @@ import DeleteEvaluatorModalController from "./delete_evaluator_modal_controller" application.register("delete-evaluator-modal", DeleteEvaluatorModalController); import UnassignEvaluatorSubmissionModalController from "./unassign_evaluator_submission_modal_controller"; -application.register("unassign-evaluator-submission-modal", UnassignEvaluatorSubmissionModalController); +application.register( + "unassign-evaluator-submission-modal", + UnassignEvaluatorSubmissionModalController +); import EvaluationCriteriaController from "./evaluation_criteria_controller"; application.register("evaluation-criteria", EvaluationCriteriaController); @@ -17,4 +20,7 @@ import EvaluationFormController from "./evaluation_form_controller"; application.register("evaluation-form", EvaluationFormController); import HotdogController from "./hotdog_controller"; -application.register("hotdog", HotdogController); \ No newline at end of file +application.register("hotdog", HotdogController); + +import ModalController from "./modal_controller"; +application.register("modal", ModalController); diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js new file mode 100644 index 00000000..2c0e3d9a --- /dev/null +++ b/app/javascript/controllers/modal_controller.js @@ -0,0 +1,55 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static targets = ["modal"]; + static values = { + modalId: String, + }; + + open(event) { + const modalId = event.currentTarget.dataset.modalTargetId; + const modal = this.modalTargets.find((modal) => modal.id === modalId); + + event.preventDefault(); + + if (modal) { + modal.showModal(); + } else { + console.error(`Modal with ID '${modalId}' not found.`); + } + } + + confirm(event) { + const modal = this._getModal(event); + + if (modal) { + const confirmRedirect = modal.dataset.modalConfirmRedirect; + + if (confirmRedirect) { + window.location.href = confirmRedirect; + } else { + modal.close(); + return true; + } + } + } + + cancel(event) { + const modal = this._getModal(event); + + if (modal) { + const cancelRedirect = modal.dataset.modalCancelRedirect; + + if (cancelRedirect) { + window.location.href = cancelRedirect; + } else { + modal.close(); + return false; + } + } + } + + _getModal(event) { + return event.currentTarget.closest("dialog"); + } +} diff --git a/app/views/evaluation_forms/_form.html.erb b/app/views/evaluation_forms/_form.html.erb index 7cc296b6..d1f5c55c 100644 --- a/app/views/evaluation_forms/_form.html.erb +++ b/app/views/evaluation_forms/_form.html.erb @@ -1,4 +1,4 @@ -<%= form_with(model: evaluation_form, data: { controller: "evaluation-form" }) do |form| %> +<%= form_with(model: evaluation_form, data: { controller: "evaluation-form modal" }) do |form| %> <% if evaluation_form.errors.any? %>

<%= pluralize(evaluation_form.errors.count, "error") %> prohibited this evaluation form from being saved:

@@ -198,16 +198,16 @@ Save
- <%= link_to "Cancel", evaluation_forms_path, data: {"modal": "cancel-modal"} %> + <%= link_to "Cancel", "#", data: {"action": "modal#open", "modal-target-id": "cancel"} %>
-<% end %> -<%= render "modals/confirmation", - id: "cancel-modal", - heading: "Are you sure you want to cancel?", - description: "Your evaluation form will not be saved and any entered information will be lost.", - confirm_text: "Yes", - confirm_redirect: evaluation_forms_path, - cancel_text: "Close" -%> \ No newline at end of file + <%= render "modals/confirmation", + id: "cancel", + heading: "Are you sure you want to cancel?", + description: "Your evaluation form will not be saved and any entered information will be lost.", + confirm_text: "Yes", + cancel_text: "Close", + confirm_redirect: evaluation_forms_path + %> +<% end %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 94294012..82cc5812 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -18,7 +18,6 @@ <%= javascript_include_tag 'session_timeout' %> - <%= javascript_include_tag 'confirmation_modal' %> <% end %> diff --git a/app/views/modals/_confirmation.erb b/app/views/modals/_confirmation.erb index 285228be..bd7cb9d0 100644 --- a/app/views/modals/_confirmation.erb +++ b/app/views/modals/_confirmation.erb @@ -3,6 +3,9 @@ class="border-width-0 radius-lg" aria-labelledby=<%= "#{id}-heading" %> aria-describedby=<%= "#{id}-description" %> + data-modal-target="modal" + data-modal-confirm-redirect="<%= defined?(confirm_redirect) ? confirm_redirect : '' %>" + data-modal-cancel-redirect="<%= defined?(cancel_redirect) ? cancel_redirect : '' %>" >
@@ -15,17 +18,8 @@

From 9f6080da704d7b5cb6d4fa92e52a4b7a9bf054b3 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Tue, 10 Dec 2024 23:38:37 -0500 Subject: [PATCH 4/4] 283 Allow modal actions. Confirm criteria removal --- .../controllers/evaluation_form_controller.js | 4 ++- .../controllers/modal_controller.js | 34 ++++++++++++++++++- .../_evaluation_criterion_fields.html.erb | 2 +- app/views/evaluation_forms/_form.html.erb | 9 +++++ app/views/modals/_confirmation.erb | 2 ++ spec/system/evaluation_form_spec.rb | 34 +++++++++++-------- 6 files changed, 68 insertions(+), 17 deletions(-) diff --git a/app/javascript/controllers/evaluation_form_controller.js b/app/javascript/controllers/evaluation_form_controller.js index 1d9fc0e0..104c6dfd 100644 --- a/app/javascript/controllers/evaluation_form_controller.js +++ b/app/javascript/controllers/evaluation_form_controller.js @@ -32,7 +32,9 @@ export default class extends Controller { // Opens all accordions, remove existing points/weights, update max points/weights values updateMaxPoints(e) { - const form = e.target.closest('form[data-controller="evaluation-form"]'); + const form = e.target.closest( + 'form[data-controller="evaluation-form modal"]' + ); const pointsWeights = form.querySelectorAll(".points-or-weight"); const weightedScale = e.target.value === "true"; diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js index 2c0e3d9a..c2eb5b9d 100644 --- a/app/javascript/controllers/modal_controller.js +++ b/app/javascript/controllers/modal_controller.js @@ -10,12 +10,14 @@ export default class extends Controller { const modalId = event.currentTarget.dataset.modalTargetId; const modal = this.modalTargets.find((modal) => modal.id === modalId); + this.openEvent = event; + event.preventDefault(); if (modal) { modal.showModal(); } else { - console.error(`Modal with ID '${modalId}' not found.`); + console.warn(`Modal with ID '${modalId}' not found.`); } } @@ -24,9 +26,13 @@ export default class extends Controller { if (modal) { const confirmRedirect = modal.dataset.modalConfirmRedirect; + const confirmAction = modal.dataset.modalConfirmAction; if (confirmRedirect) { window.location.href = confirmRedirect; + } else if (confirmAction) { + this.invokeAction(confirmAction); + modal.close(); } else { modal.close(); return true; @@ -39,9 +45,13 @@ export default class extends Controller { if (modal) { const cancelRedirect = modal.dataset.modalCancelRedirect; + const cancelAction = modal.dataset.modalCancelAction; if (cancelRedirect) { window.location.href = cancelRedirect; + } else if (cancelAction) { + this.invokeAction(cancelAction); + modal.close(); } else { modal.close(); return false; @@ -52,4 +62,26 @@ export default class extends Controller { _getModal(event) { return event.currentTarget.closest("dialog"); } + + invokeAction(actionName) { + const [controllerName, action] = actionName.split("#"); + const controllerElement = document.querySelector( + `[data-controller~="${controllerName}"]` + ); + + if (!controllerElement) { + console.warn(`Controller element for ${controllerName} not found.`); + } + + const controller = this.application.getControllerForElementAndIdentifier( + controllerElement, + controllerName + ); + + if (controller && typeof controller[action] === "function") { + controller[action](this.openEvent); + } else { + console.warn(`Action ${actionName} not found on ${controllerName}`); + } + } } diff --git a/app/views/evaluation_forms/_evaluation_criterion_fields.html.erb b/app/views/evaluation_forms/_evaluation_criterion_fields.html.erb index 9250bafc..63302709 100644 --- a/app/views/evaluation_forms/_evaluation_criterion_fields.html.erb +++ b/app/views/evaluation_forms/_evaluation_criterion_fields.html.erb @@ -183,7 +183,7 @@ <% if !form_disabled %>
- <%= button_tag type: "button", id: criteria_field_id(f, "delete_criteria", is_template), class: "delete-criteria-button usa-button usa-button--unstyled", title: "Delete criteria", data: {action: "click->evaluation-criteria#removeCriteria"} do %> + <%= button_tag type: "button", id: criteria_field_id(f, "delete_criteria", is_template), class: "delete-criteria-button usa-button usa-button--unstyled", title: "Delete criteria", data: {"action": "modal#open", "modal-target-id": "remove-criteria"} do %> Remove criteria <% end %>
diff --git a/app/views/evaluation_forms/_form.html.erb b/app/views/evaluation_forms/_form.html.erb index d1f5c55c..21858873 100644 --- a/app/views/evaluation_forms/_form.html.erb +++ b/app/views/evaluation_forms/_form.html.erb @@ -210,4 +210,13 @@ cancel_text: "Close", confirm_redirect: evaluation_forms_path %> + + <%= render "modals/confirmation", + id: "remove-criteria", + heading: "Are you sure you want to remove this criteria?", + description: "", + confirm_text: "Yes", + cancel_text: "No", + confirm_action: "evaluation-criteria#removeCriteria" + %> <% end %> diff --git a/app/views/modals/_confirmation.erb b/app/views/modals/_confirmation.erb index bd7cb9d0..0bf45f02 100644 --- a/app/views/modals/_confirmation.erb +++ b/app/views/modals/_confirmation.erb @@ -6,6 +6,8 @@ data-modal-target="modal" data-modal-confirm-redirect="<%= defined?(confirm_redirect) ? confirm_redirect : '' %>" data-modal-cancel-redirect="<%= defined?(cancel_redirect) ? cancel_redirect : '' %>" + data-modal-confirm-action="<%= defined?(confirm_action) ? confirm_action : '' %>" + data-modal-cancel-action="<%= defined?(cancel_action) ? cancel_action : '' %>" >
diff --git a/spec/system/evaluation_form_spec.rb b/spec/system/evaluation_form_spec.rb index 1cefe84e..94632f88 100644 --- a/spec/system/evaluation_form_spec.rb +++ b/spec/system/evaluation_form_spec.rb @@ -21,7 +21,7 @@ click_link_or_button "Cancel" - assert_selector 'dialog#cancel-modal', visible: true + assert_selector 'dialog#cancel', visible: true expect(page).to(be_axe_clean) end @@ -31,9 +31,9 @@ click_link_or_button "Cancel" - assert_selector 'dialog#cancel-modal', visible: true + assert_selector 'dialog#cancel', visible: true - within 'dialog#cancel-modal' do + within 'dialog#cancel' do click_link_or_button 'Yes' end @@ -45,13 +45,13 @@ click_link_or_button "Cancel" - assert_selector 'dialog#cancel-modal', visible: true + assert_selector 'dialog#cancel', visible: true - within 'dialog#cancel-modal' do + within 'dialog#cancel' do click_link_or_button 'Close' end - assert_no_selector 'dialog#cancel-modal', visible: true + assert_no_selector 'dialog#cancel', visible: true assert_current_path new_evaluation_form_path end @@ -305,7 +305,7 @@ click_link_or_button "Cancel" - assert_selector 'dialog#cancel-modal', visible: true + assert_selector 'dialog#cancel', visible: true expect(page).to(be_axe_clean) end @@ -315,9 +315,9 @@ click_link_or_button "Cancel" - assert_selector 'dialog#cancel-modal', visible: true + assert_selector 'dialog#cancel', visible: true - within 'dialog#cancel-modal' do + within 'dialog#cancel' do click_link_or_button 'Yes' end @@ -329,13 +329,13 @@ click_link_or_button "Cancel" - assert_selector 'dialog#cancel-modal', visible: true + assert_selector 'dialog#cancel', visible: true - within 'dialog#cancel-modal' do + within 'dialog#cancel' do click_link_or_button 'Close' end - assert_no_selector 'dialog#cancel-modal', visible: true + assert_no_selector 'dialog#cancel', visible: true assert_current_path edit_evaluation_form_path(evaluation_form) end @@ -425,7 +425,7 @@ visit edit_evaluation_form_path(closed_evaluation_form) # Add expectation in spec to satisfy rubocop - expect(page).to have_css("form[data-controller='evaluation-form']") + expect(page).to have_css("form[data-controller='evaluation-form modal']") check_all_non_hidden_inputs_disabled_except_end_date end end @@ -567,6 +567,12 @@ def add_criterion def remove_criterion(index) click_link_or_button "evaluation_form_evaluation_criteria_attributes_#{index}_delete_criteria" + + assert_selector 'dialog#remove-criteria', visible: true + + within 'dialog#remove-criteria' do + click_link_or_button 'Yes' + end end def toggle_criteria_accordion(index) @@ -815,7 +821,7 @@ def rebalance_criteria_weights # Checks that all non hidden or end date fields are disabled def check_all_non_hidden_inputs_disabled_except_end_date - within("form[data-controller='evaluation-form']") do + within("form[data-controller='evaluation-form modal']") do all("input:not([type='hidden']), textarea, select").each do |field| if field[:id] == "evaluation_form_closing_date" expect(field).not_to be_disabled, "Expected #{field[:id]} to not be disabled"