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;