From 6a86c267f4536d414534efdc67a8d7cda1ec6bfe Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 25 May 2026 15:13:12 +0000 Subject: [PATCH] [#68734] Migrate webhooks form jQuery to Stimulus Replaces jQuery-based enable/disable logic in the webhooks admin form with a new Stimulus controller `disable-when-value-selected`. This change was originally deferred from PR #20884 as it was planned to be addressed in Work Package #69436 / PR #21227. Since that work has been put on hold and jQuery removal is progressing, this commit completes the migration now. Changes: - Add DisableWhenValueSelectedController for declarative form toggling - Add toggleEnabled/enableElement/disableElement helpers to dom-helpers - Remove jQuery code from webhooks admin form - Apply Stimulus controller to project selection fieldset Related to opf/openproject#20884 https://community.openproject.org/wp/68734 --- .../src/app/shared/helpers/dom-helpers.ts | 32 +++++++++ .../disable-when-value-selected.controller.ts | 69 +++++++++++++++++++ frontend/src/stimulus/setup.ts | 2 + .../webhooks/outgoing/admin/_form.html.erb | 34 ++++----- 4 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 frontend/src/stimulus/controllers/disable-when-value-selected.controller.ts diff --git a/frontend/src/app/shared/helpers/dom-helpers.ts b/frontend/src/app/shared/helpers/dom-helpers.ts index 635c2da2e9e..03991e1fcb0 100644 --- a/frontend/src/app/shared/helpers/dom-helpers.ts +++ b/frontend/src/app/shared/helpers/dom-helpers.ts @@ -243,3 +243,35 @@ export function attributeTokenList(element:HTMLElement, attribute:string):DOMTok return list as DOMTokenList; } /* eslint-enable */ + +/** + * Toggles the enabled/disabled state of form elements. + * For fieldsets, recursively applies to all child elements. + * + * @param element the element to toggle + * @param value force state (optional): `true` to enable/`false` to disable + */ +export function toggleEnabled(element:HTMLElement, value?:boolean) { + if ( + element instanceof HTMLInputElement || + element instanceof HTMLSelectElement || + element instanceof HTMLTextAreaElement || + element instanceof HTMLButtonElement || + element instanceof HTMLFieldSetElement + ) { + if (typeof value === 'undefined') { + element.disabled = !element.disabled; + } else { + element.disabled = !value; + } + } + + if (element instanceof HTMLFieldSetElement) { + Array.from(element.elements).forEach((child) => { + toggleEnabled(child as HTMLElement, !element.disabled); + }); + } +} + +export const enableElement = (element:HTMLElement) => toggleEnabled(element, true); +export const disableElement = (element:HTMLElement) => toggleEnabled(element, false); diff --git a/frontend/src/stimulus/controllers/disable-when-value-selected.controller.ts b/frontend/src/stimulus/controllers/disable-when-value-selected.controller.ts new file mode 100644 index 00000000000..61582c71715 --- /dev/null +++ b/frontend/src/stimulus/controllers/disable-when-value-selected.controller.ts @@ -0,0 +1,69 @@ +/* + * -- copyright + * OpenProject is an open source project management software. + * Copyright (C) the OpenProject GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 3. + * + * OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: + * Copyright (C) 2006-2013 Jean-Philippe Lang + * Copyright (C) 2010-2013 the ChiliProject Team + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * See COPYRIGHT and LICENSE files for more details. + * ++ + */ + +import { toggleEnabled } from 'core-app/shared/helpers/dom-helpers'; +import { ApplicationController } from 'stimulus-use'; + +export default class OpDisableWhenValueSelectedController extends ApplicationController { + static targets = ['cause', 'effect']; + + declare readonly effectTargets:(HTMLInputElement|HTMLFieldSetElement)[]; + + private boundListener = this.toggleDisabled.bind(this); + + causeTargetConnected(target:HTMLElement) { + target.addEventListener('change', this.boundListener); + } + + causeTargetDisconnected(target:HTMLElement) { + target.removeEventListener('change', this.boundListener); + } + + private toggleDisabled(evt:Event):void { + const input = evt.target as HTMLInputElement; + const targetName = input.dataset.targetName; + + this + .effectTargets + .filter((el) => targetName === el.dataset.targetName) + .forEach((el) => { + const disabled = this.willDisable(el, input.value); + toggleEnabled(el, !disabled); + }); + } + + private willDisable(el:HTMLElement, value:string):boolean { + if (el.dataset.notValue) { + return el.dataset.notValue === value; + } + + return !(el.dataset.value === value); + } +} diff --git a/frontend/src/stimulus/setup.ts b/frontend/src/stimulus/setup.ts index 2fa3ac2c098..da140656c9a 100644 --- a/frontend/src/stimulus/setup.ts +++ b/frontend/src/stimulus/setup.ts @@ -2,6 +2,7 @@ import { environment } from '../environments/environment'; import { OpApplicationController } from './controllers/op-application.controller'; import MainMenuController from './controllers/dynamic/menus/main.controller'; import OpDisableWhenCheckedController from './controllers/disable-when-checked.controller'; +import OpDisableWhenValueSelectedController from './controllers/disable-when-value-selected.controller'; import PrintController from './controllers/print.controller'; import RefreshOnFormChangesController from './controllers/refresh-on-form-changes.controller'; import FormPreviewController from './controllers/form-preview.controller'; @@ -57,6 +58,7 @@ OpenProjectStimulusApplication.preregister('application', OpApplicationControlle OpenProjectStimulusApplication.preregister('async-dialog', AsyncDialogController); OpenProjectStimulusApplication.preregister('disable-when-checked', OpDisableWhenCheckedController); OpenProjectStimulusApplication.preregister('disable-when-clicked', DisableWhenClickedController); +OpenProjectStimulusApplication.preregister('disable-when-value-selected', OpDisableWhenValueSelectedController); OpenProjectStimulusApplication.preregister('flash', FlashController); OpenProjectStimulusApplication.preregister('menus--main', MainMenuController); OpenProjectStimulusApplication.preregister('require-password-confirmation', RequirePasswordConfirmationController); diff --git a/modules/webhooks/app/views/webhooks/outgoing/admin/_form.html.erb b/modules/webhooks/app/views/webhooks/outgoing/admin/_form.html.erb index 0cd3804082f..5967756c12d 100644 --- a/modules/webhooks/app/views/webhooks/outgoing/admin/_form.html.erb +++ b/modules/webhooks/app/views/webhooks/outgoing/admin/_form.html.erb @@ -70,7 +70,7 @@ <% end %> -
+
<%= t "webhooks.outgoing.form.project_ids.title" %> @@ -80,14 +80,22 @@ "all", checked: @webhook.all_projects?, label: t("webhooks.outgoing.form.project_ids.all"), - container_class: "-wide" %> + container_class: "-wide", + data: { + disable_when_value_selected_target: "cause", + target_name: "webhook_project_ids" + } %>
<%= f.radio_button :project_ids, "selection", checked: !@webhook.all_projects?, label: t("webhooks.outgoing.form.project_ids.selected"), - container_class: "-wide" %> + container_class: "-wide", + data: { + disable_when_value_selected_target: "cause", + target_name: "webhook_project_ids" + } %>
@@ -99,23 +107,15 @@ id, !@webhook.all_projects? && @webhook.project_ids.include?(id), disabled: @webhook.all_projects?, - class: "webhooks--selected-project-ids" -%> + class: "webhooks--selected-project-ids", + data: { + disable_when_value_selected_target: "effect", + target_name: "webhook_project_ids", + value: "selection" + } -%> <%= name %> <% end %>
- -<%= nonced_javascript_tag do %> - (function($) { - // Toggle selector for new/edit webhooks projects - $('input[name="webhook[project_ids]"]').change(function(){ - $('.webhooks--selected-project-ids').prop('disabled', $(this).val() === 'all'); - }); - - $('input[name="webhook[type_ids]"]').change(function(){ - $('.webhooks--selected-type-ids').prop('disabled', $(this).val() === 'all'); - }); - }(jQuery)); -<% end %>