From a1ae489719a3df7d0689f4e9946bb6c86c741353 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Wed, 11 Dec 2024 16:19:51 -0500 Subject: [PATCH 1/2] [283] Eval Form: Cancelation modal (#315) * 283 Initial cancel modal for eval form * 283 Add accessibility test for cancel modal * 283 Change confirm/cancel modal to stimulus * 283 Allow modal actions. Confirm criteria removal --------- Co-authored-by: Stephen Chudleigh --- .../controllers/evaluation_form_controller.js | 4 +- app/javascript/controllers/index.js | 10 ++- .../controllers/modal_controller.js | 87 ++++++++++++++++++ .../_evaluation_criterion_fields.html.erb | 2 +- app/views/evaluation_forms/_form.html.erb | 26 +++++- app/views/modals/_confirmation.erb | 28 ++++++ spec/system/evaluation_form_spec.rb | 88 ++++++++++++++++++- 7 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 app/javascript/controllers/modal_controller.js create mode 100644 app/views/modals/_confirmation.erb 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/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..c2eb5b9d --- /dev/null +++ b/app/javascript/controllers/modal_controller.js @@ -0,0 +1,87 @@ +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); + + this.openEvent = event; + + event.preventDefault(); + + if (modal) { + modal.showModal(); + } else { + console.warn(`Modal with ID '${modalId}' not found.`); + } + } + + confirm(event) { + const modal = this._getModal(event); + + 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; + } + } + } + + cancel(event) { + const modal = this._getModal(event); + + 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; + } + } + } + + _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 6c327f2a..8a36bb7a 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:

@@ -200,7 +200,25 @@ Save
- <%= link_to "Cancel", evaluation_forms_path %> -
+ <%= link_to "Cancel", "#", data: {"action": "modal#open", "modal-target-id": "cancel"} %> +
-<% end %> \ 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 + %> + + <%= 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 new file mode 100644 index 00000000..0bf45f02 --- /dev/null +++ b/app/views/modals/_confirmation.erb @@ -0,0 +1,28 @@ + + 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 : '' %>" + data-modal-confirm-action="<%= defined?(confirm_action) ? confirm_action : '' %>" + data-modal-cancel-action="<%= defined?(cancel_action) ? cancel_action : '' %>" +> +
+
+

> + <%= 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 03ec9b67..6cc66f87 100644 --- a/spec/system/evaluation_form_spec.rb +++ b/spec/system/evaluation_form_spec.rb @@ -16,6 +16,45 @@ 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', visible: true + + expect(page).to(be_axe_clean) + 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', visible: true + + within 'dialog#cancel' 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', visible: true + + within 'dialog#cancel' do + click_link_or_button 'Close' + end + + assert_no_selector 'dialog#cancel', 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 @@ -291,6 +330,45 @@ 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', visible: true + + expect(page).to(be_axe_clean) + 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', visible: true + + within 'dialog#cancel' 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', visible: true + + within 'dialog#cancel' do + click_link_or_button 'Close' + end + + assert_no_selector 'dialog#cancel', 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) @@ -377,7 +455,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 @@ -519,6 +597,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) @@ -791,7 +875,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" From 348d3af8812468a61fea8ea11c434b3d1ee8135c Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Thu, 12 Dec 2024 02:15:02 -0500 Subject: [PATCH 2/2] [181] Evaluation Form Validation - Part 1 (#316) * 181 Fix missing elements eval form fail validation * 181 Fix failure from new form default scale type --------- Co-authored-by: Stephen Chudleigh --- .../evaluation_forms/_evaluation_criterion_fields.html.erb | 4 ++-- app/views/evaluation_forms/_form.html.erb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/evaluation_forms/_evaluation_criterion_fields.html.erb b/app/views/evaluation_forms/_evaluation_criterion_fields.html.erb index 63302709..5a375040 100644 --- a/app/views/evaluation_forms/_evaluation_criterion_fields.html.erb +++ b/app/views/evaluation_forms/_evaluation_criterion_fields.html.erb @@ -114,7 +114,7 @@ -
+
<%= f.label :rating_scale_options, "Rating Scale Options", for: criteria_field_id(f, "rating_scale_options", is_template), class: "text-bold" %>
@@ -161,7 +161,7 @@
<% (0..10).each do |index| %> - <% disabledOptionLabel = is_template || !f.object.persisted? || f.object.numeric? || ((f.object.option_range_start && index < f.object.option_range_start) || (f.object.option_range_end && index > f.object.option_range_end)) %> + <% disabledOptionLabel = is_template || !f.object.scoring_type.present? || f.object.numeric? || ((f.object.option_range_start && index < f.object.option_range_start) || (f.object.option_range_end && index > f.object.option_range_end)) %>
diff --git a/app/views/evaluation_forms/_form.html.erb b/app/views/evaluation_forms/_form.html.erb index 8a36bb7a..1a273b59 100644 --- a/app/views/evaluation_forms/_form.html.erb +++ b/app/views/evaluation_forms/_form.html.erb @@ -101,7 +101,7 @@ name="evaluation_form[weighted_scoring]" value="false" data-action="click->evaluation-form#updateMaxPoints" - <%= 'checked' if !evaluation_form.weighted_scoring? && evaluation_form.persisted? %> + <%= 'checked' if evaluation_form.weighted_scoring == false && evaluation_form.persisted? %> <%= 'disabled' if disabled %> required > @@ -115,7 +115,7 @@ name="evaluation_form[weighted_scoring]" value="true" data-action="click->evaluation-form#updateMaxPoints" - <%= 'checked' if evaluation_form.weighted_scoring? %> + <%= 'checked' if evaluation_form.weighted_scoring == true %> <%= 'disabled' if disabled %> >