diff --git a/app/components/projects/configure_view_modal_component.html.erb b/app/components/projects/configure_view_modal_component.html.erb
index 1c954bcf2099..ed3df3f462e0 100644
--- a/app/components/projects/configure_view_modal_component.html.erb
+++ b/app/components/projects/configure_view_modal_component.html.erb
@@ -1,7 +1,6 @@
<%= render(Primer::Alpha::Dialog.new(title: t(:'queries.configure_view.heading'),
size: :large,
id: MODAL_ID,
- data: { controller: "pagination-reset-on-sort", application_target: "dynamic" },
# Hack to give the draggable autcompleter (ng-select) bound to the dialog
# enough height to display all options.
# This is necessary as long as ng-select does not support popovers.
@@ -11,15 +10,11 @@
<%= primer_form_with(
url: projects_path,
id: QUERY_FORM_ID,
- data: {
- "turbo-stream" => false,
- "pagination-reset-on-sort-target" => "form",
- action: "pagination-reset-on-sort#submit"
- },
+ data: { "turbo-stream" => false },
method: :get
) do |form| %>
<% helpers.projects_query_params.except(:columns, :sortBy).each do |name, value| %>
- <%= hidden_field_tag name, value, data: {"pagination-reset-on-sort-target" => name} %>
+ <%= hidden_field_tag name, value, data: {"sort-by-config-target" => name} %>
<% end %>
<%= render(Primer::Alpha::TabPanels.new(label: "label")) do |tab_panel| %>
<% tab_panel.with_tab(selected: true, id: "tab-selects--columns") do |tab| %>
diff --git a/app/components/queries/sort_by_component.html.erb b/app/components/queries/sort_by_component.html.erb
index ff4548f6a53d..041fe0328bf2 100644
--- a/app/components/queries/sort_by_component.html.erb
+++ b/app/components/queries/sort_by_component.html.erb
@@ -1,9 +1,7 @@
<%= render(Primer::Beta::Heading.new(tag: :h5)) { I18n.t('queries.configure_view.sort_by.automatic.heading') } %>
<%= render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) { I18n.t('queries.configure_view.sort_by.automatic.description', plural: queried_model_name.plural) } %>
- <%= hidden_field_tag :sortBy, current_orders, data: {
- "sort-by-config-target" => "sortByField",
- "pagination-reset-on-sort-target" => "sortByField" } %>
+ <%= hidden_field_tag :sortBy, current_orders, data: { "sort-by-config-target" => "sortByField" } %>
<%= render(Primer::OpenProject::FlexLayout.new(data: { "sort-by-config-target" => "inputRowContainer" })) do |layout| %>
<% order_limit.times do |i| %>
<% layout.with_row(mt: 3, data: { "sort-by-config-target" => "inputRow" }) do %>
diff --git a/frontend/src/stimulus/controllers/dynamic/pagination-reset-on-sort.controller.ts b/frontend/src/stimulus/controllers/dynamic/pagination-reset-on-sort.controller.ts
deleted file mode 100644
index 3654eef6c2cf..000000000000
--- a/frontend/src/stimulus/controllers/dynamic/pagination-reset-on-sort.controller.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * -- 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 { Controller } from '@hotwired/stimulus';
-
-export default class PaginationResetOnSort extends Controller {
- static targets = [
- 'sortByField',
- 'form',
- 'page',
- ];
-
- declare readonly sortByFieldTarget:HTMLInputElement;
- declare readonly formTarget:HTMLFormElement;
- declare readonly pageTarget:HTMLInputElement;
-
- declare sortByFieldInitialSelection:string;
-
- connect():void {
- this.sortByFieldInitialSelection = this.sortByFieldTarget.value;
- }
-
- submit(event:Event):void {
- event.preventDefault();
- const currentSelection = this.sortByFieldTarget.value;
-
- if (this.sortByFieldInitialSelection !== currentSelection) {
- // When the sorting criteria changes, reset the pagination to the first page.
- // This leads to a better UX since the previous position in the pagination becomes
- // obsolete when the underlying collection changes due to sorting adjustments. It is better to start
- // from a fresh perspective on the data.
- this.pageTarget.value = '';
- }
-
- this.formTarget.submit();
- }
-}
diff --git a/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts b/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts
index 1a8065327216..ae2b4c25ee16 100644
--- a/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts
+++ b/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts
@@ -50,6 +50,11 @@ export default class SortByConfigController extends Controller {
declare readonly inputRowTargets:HTMLElement[];
declare readonly inputRowContainerTarget:HTMLElement;
+ declare parentForm:HTMLFormElement | null;
+ declare pageTarget:HTMLInputElement;
+
+ declare initialSortBy:string;
+
connect():void {
this.inputRowTargets.forEach((row) => {
this.manageRow(row);
@@ -57,6 +62,9 @@ export default class SortByConfigController extends Controller {
this.displayNewFieldSelectorIfNeeded();
this.disableSelectedFieldsForOtherSelects();
+
+ this.initialSortBy = this.sortByFieldTarget.value;
+ this.registerPaginationResetHandler();
}
buildSortJson():string {
@@ -69,6 +77,43 @@ export default class SortByConfigController extends Controller {
return JSON.stringify(compact(filters));
}
+ // Tries to find the parent form in the DOM. If present and the form contains a `page` field marked
+ // with the proper target, will register a handler to trigger a pagination reset when the sorting
+ // changes.
+ registerPaginationResetHandler():void {
+ this.parentForm = this.sortByFieldTarget.closest('form');
+
+ if (this.parentForm) {
+ this.pageTarget = this.parentForm.querySelector('input[data-sort-by-config-target="page"]') as HTMLInputElement;
+
+ if (this.pageTarget) {
+ this.parentForm.addEventListener('submit', this.onFormSubmit.bind(this));
+ }
+ }
+ }
+
+ onFormSubmit(event:SubmitEvent):void {
+ if (!this.parentForm || !this.pageTarget) { return; }
+ event.preventDefault();
+
+ this.resetPaginationIfSortingChanged();
+
+ this.parentForm.submit();
+ }
+
+ // When the sorting criteria changes, reset the pagination to the first page.
+ // This leads to a better UX since the previous position in the pagination becomes
+ // obsolete when the underlying collection changes due to sorting adjustments. It is better to start
+ // from a fresh perspective on the data.
+ resetPaginationIfSortingChanged():void {
+ const currentSelection = this.sortByFieldTarget.value;
+
+ if (this.initialSortBy !== currentSelection) {
+ // Reset the pagination:
+ this.pageTarget.value = '';
+ }
+ }
+
fieldChanged(event:Event):void {
const target = event.target as HTMLElement;
const row = target.closest('div[data-sort-by-config-target="inputRow"]') as HTMLElement;