diff --git a/app/models/custom_actions/actions/base.rb b/app/models/custom_actions/actions/base.rb
index 8b64662449b5..4995d3c9371d 100644
--- a/app/models/custom_actions/actions/base.rb
+++ b/app/models/custom_actions/actions/base.rb
@@ -43,6 +43,12 @@ def allowed_values
raise NotImplementedError
end
+ def value_objects
+ values.map do |value|
+ allowed_values.find { |v| v[:value] == value }
+ end
+ end
+
def type
raise NotImplementedError
end
diff --git a/app/models/custom_actions/conditions/base.rb b/app/models/custom_actions/conditions/base.rb
index 93828f8d3880..ddd0a58fdd30 100644
--- a/app/models/custom_actions/conditions/base.rb
+++ b/app/models/custom_actions/conditions/base.rb
@@ -45,6 +45,12 @@ def allowed_values
.map { |value, label| { value:, label: } }
end
+ def value_objects
+ values.map do |value|
+ allowed_values.find { |v| v[:value] == value }
+ end
+ end
+
def human_name
WorkPackage.human_attribute_name(self.class.key)
end
diff --git a/app/views/augmented/_autocomplete_select_decoration.html.erb b/app/views/augmented/_autocomplete_select_decoration.html.erb
deleted file mode 100644
index 62f4ad43bb8d..000000000000
--- a/app/views/augmented/_autocomplete_select_decoration.html.erb
+++ /dev/null
@@ -1,9 +0,0 @@
-
- <%= angular_component_tag(
- 'opce-select-decoration',
- inputs: inputs.merge(
- options: select_options,
- )
- ) %>
-
-
diff --git a/app/views/custom_actions/_form.html.erb b/app/views/custom_actions/_form.html.erb
index 1858ded3b327..2791dd2fdf56 100644
--- a/app/views/custom_actions/_form.html.erb
+++ b/app/views/custom_actions/_form.html.erb
@@ -32,18 +32,17 @@
}
%>
<% else %>
- <% selected_values = condition.values
- select_options = condition.allowed_values.map { |v| { label: v[:label], value: v[:value], selected: selected_values.include?(v[:value]) } } %>
- <%= render partial: 'augmented/autocomplete_select_decoration',
- locals: {
- select_options:,
- inputs: {
- inputName: input_name,
- inputId: "custom_action_conditions_#{condition.key}",
- multiple: true,
- key: condition.key.to_s
- }
- } %>
+ <%= angular_component_tag 'opce-autocompleter',
+ inputs: {
+ multiple: true,
+ defaultData: false,
+ items: condition.allowed_values.map { |v| { id: v[:value], name: v[:label] } },
+ model: condition.value_objects.map { |v| { id: v[:value], name: v[:label] } },
+ inputName: input_name,
+ inputId: "custom_action_conditions_#{condition.key}",
+ appendTo: "body",
+ }
+ %>
<% end %>
@@ -79,18 +78,17 @@
}
%>
<% else %>
- <% selected_values = action.values
- select_options = action.allowed_values.map { |v| { label: v[:label], value: v[:value], selected: selected_values.include?(v[:value]) } } %>
- <%= render partial: 'augmented/autocomplete_select_decoration',
- locals: {
- select_options:,
- inputs: {
- inputName: input_name,
- inputId: "custom_action_actions_#{action.key}",
- multiple: action.multi_value?,
- key: action.key.to_s
- }
- } %>
+ <%= angular_component_tag 'opce-autocompleter',
+ inputs: {
+ multiple: true,
+ defaultData: false,
+ items: action.allowed_values.map { |v| { id: v[:value], name: v[:label] } },
+ model: action.value_objects.map { |v| { id: v[:value], name: v[:label] } },
+ inputName: input_name,
+ inputId: "custom_action_conditions_#{action.key}",
+ appendTo: "body",
+ }
+ %>
<% end %>
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 51d140173014..dc2e45c2eea4 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -123,9 +123,6 @@ import {
import {
ProjectAutocompleterComponent,
} from 'core-app/shared/components/autocompleter/project-autocompleter/project-autocompleter.component';
-import {
- AutocompleteSelectDecorationComponent,
-} from 'core-app/shared/components/autocompleter/autocomplete-select-decoration/autocomplete-select-decoration.component';
import {
MembersAutocompleterComponent,
} from 'core-app/shared/components/autocompleter/members-autocompleter/members-autocompleter.component';
@@ -426,7 +423,6 @@ export class OpenProjectModule implements DoBootstrap {
registerCustomElement('opce-global-search', GlobalSearchInputComponent, { injector });
registerCustomElement('opce-autocompleter', OpAutocompleterComponent, { injector });
registerCustomElement('opce-project-autocompleter', ProjectAutocompleterComponent, { injector });
- registerCustomElement('opce-select-decoration', AutocompleteSelectDecorationComponent, { injector });
registerCustomElement('opce-members-autocompleter', MembersAutocompleterComponent, { injector });
registerCustomElement('opce-user-autocompleter', UserAutocompleterComponent, { injector });
registerCustomElement('opce-macro-attribute-value', AttributeValueMacroComponent, { injector });
diff --git a/frontend/src/app/shared/components/autocompleter/autocomplete-select-decoration/autocomplete-select-decoration.component.ts b/frontend/src/app/shared/components/autocompleter/autocomplete-select-decoration/autocomplete-select-decoration.component.ts
deleted file mode 100644
index 897fdbb3deea..000000000000
--- a/frontend/src/app/shared/components/autocompleter/autocomplete-select-decoration/autocomplete-select-decoration.component.ts
+++ /dev/null
@@ -1,134 +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 { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
-import { NgSelectComponent } from '@ng-select/ng-select';
-import {
- IAutocompleteItem,
- OpAutocompleterComponent,
-} from 'core-app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component';
-import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
-
-type SelectItem = { label:string, value:string, selected?:boolean };
-
-export const autocompleteSelectDecorationSelector = 'autocomplete-select-decoration';
-
-@Component({
- template: `
-
-
- {{ item.label }}
-
-
- `,
- selector: autocompleteSelectDecorationSelector,
-})
-export class AutocompleteSelectDecorationComponent extends OpAutocompleterComponent implements OnInit, AfterViewInit {
- @ViewChild(NgSelectComponent) public ngSelectComponent:NgSelectComponent;
-
- public options:SelectItem[];
-
- /** Get the selected options */
- public selected:SelectItem|SelectItem[];
-
- /** The field key (e.g. status, type, or project) */
- @Input() key:string;
-
- text = {
- placeholder: this.I18n.t('js.placeholders.selection'),
- };
-
- ngOnInit():void {
- populateInputsFromDataset(this);
- const element = this.elementRef.nativeElement as HTMLElement;
-
- // Add Rails multiple identifier if multiple
- if (this.multiple) {
- this.inputName += '[]';
- }
-
- // Prepare and build the options
- // Expected array of objects with id, name, select
- const data:SelectItem[] = JSON.parse(element.dataset.options!);
-
- // Set initial selection
- this.setInitialSelection(data);
-
- if (!this.multiple) {
- this.selected = (this.selected as SelectItem[])[0];
- }
-
- this.options = data;
-
- // Unhide the parent
- element.parentElement!.hidden = false;
- }
-
- // eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method
- ngAfterViewInit():void {
- // do nothing and prevent the parent hook to be called
- }
-
- setInitialSelection(data:SelectItem[]):void {
- this.updateSelection(data.filter((element) => element.selected));
- }
-
- updateSelection(items:SelectItem|SelectItem[]):void {
- this.selected = items;
- items = _.castArray(items);
-
- this.removeCurrentSyncedFields();
- items.forEach((el:SelectItem) => {
- this.createSyncedField(el.value);
- });
- }
-
- createSyncedField(value:string):void {
- const element = jQuery(this.elementRef.nativeElement);
- element
- .parent()
- .append(``);
- }
-
- removeCurrentSyncedFields():void {
- const element = jQuery(this.elementRef.nativeElement);
- element
- .parent()
- .find(`input[name="${this.inputName}"]`)
- .remove();
- }
-}
diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.ts b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.ts
index 87a41864d2c5..210d04678aa9 100644
--- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.ts
+++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.ts
@@ -146,7 +146,7 @@ export class OpAutocompleterComponent(null);
@Input() public clearSearchOnAdd?:boolean = true;
@@ -314,6 +314,10 @@ export class OpAutocompleterComponent('');
}
+ if (this.items) {
+ this.items$.next(this.items as IOPAutocompleterOption[]);
+ }
+
if (this.inputValue && !this.model) {
this
.opAutocompleterService
@@ -328,8 +332,7 @@ export class OpAutocompleterComponent
<%= content_tag(:div, **@field_wrap_arguments) do %>
- <% if decorated_select? %>
- <%= render partial: '/augmented/autocomplete_select_decoration',
- formats: %i[html],
- locals: {
- inputs: @autocomplete_options.merge(
- classes: "ng-select--primerized #{@input.invalid? ? '-error' : ''}",
- inputName: @autocomplete_options.fetch(:inputName) { builder.field_name(@input.name) },
- inputId: @autocomplete_options.fetch(:inputId) { builder.field_id(@input.name) },
- inputValue: @autocomplete_options.fetch(:inputValue) { builder.object.send(@input.name) },
- key: @autocomplete_options.fetch(:resource, ''),
- defaultData: @autocomplete_options.fetch(:defaultData) { true }
- ),
- select_options: select_options.map(&:to_h),
- } %>
- <% else %>
- <%= content_tag(:div, class: ("projects-autocomplete-with-search-icon" if @autocomplete_options.delete(:with_search_icon))) do %>
- <%= angular_component_tag @autocomplete_options.fetch(:component),
- data: @autocomplete_options.delete(:data) { {} },
- inputs: @autocomplete_options.merge(
- classes: "ng-select--primerized #{@input.invalid? ? '-error' : ''}",
- inputName: @autocomplete_options.fetch(:inputName) { builder.field_name(@input.name) },
- inputValue: @autocomplete_options.fetch(:inputValue) { builder.object.send(@input.name) },
- defaultData: @autocomplete_options.fetch(:defaultData) { true }
- )
- %>
- <% end %>
+ <%= content_tag(:div, class: ("projects-autocomplete-with-search-icon" if @with_search_icon)) do %>
+ <%= angular_component_tag(@autocomplete_component,
+ data: @autocomplete_data,
+ inputs: @autocomplete_inputs) %>
<% end %>
<% end %>
<% end %>
diff --git a/lib/primer/open_project/forms/autocompleter.rb b/lib/primer/open_project/forms/autocompleter.rb
index f2d35812d62a..c274e9449739 100644
--- a/lib/primer/open_project/forms/autocompleter.rb
+++ b/lib/primer/open_project/forms/autocompleter.rb
@@ -8,17 +8,37 @@ class Autocompleter < Primer::Forms::BaseComponent
include AngularHelper
prepend WrappedInput
- delegate :builder, :form, :select_options, to: :@input
+ delegate :builder, :form, to: :@input
def initialize(input:, autocomplete_options:, wrapper_data_attributes: {})
super()
@input = input
- @autocomplete_options = autocomplete_options
+ @with_search_icon = autocomplete_options.delete(:with_search_icon) { false }
+ @autocomplete_component = autocomplete_options.delete(:component) { "opce-autocompleter" }
+ @autocomplete_data = autocomplete_options.delete(:data) { {} }
+ @autocomplete_inputs = extend_autocomplete_inputs(autocomplete_options)
@wrapper_data_attributes = wrapper_data_attributes
end
- def decorated_select?
- @autocomplete_options[:decorated]
+ def extend_autocomplete_inputs(inputs) # rubocop:disable Metrics/AbcSize
+ inputs[:classes] = "ng-select--primerized #{@input.invalid? ? '-error' : ''}"
+ inputs[:inputName] ||= builder.field_name(@input.name)
+ inputs[:inputValue] ||= builder.object.send(@input.name)
+ inputs[:defaultData] ||= true
+
+ if inputs.delete(:decorated)
+ inputs[:items] = @input.select_options.map(&:to_h)
+ inputs[:model] = selected_options
+ inputs[:defaultData] = false
+ end
+
+ inputs
+ end
+
+ def selected_options
+ @input.select_options.filter_map do |item|
+ item.value if item.selected
+ end
end
end
end
diff --git a/lib/primer/open_project/forms/dsl/autocompleter_input.rb b/lib/primer/open_project/forms/dsl/autocompleter_input.rb
index 497658f27d91..11cf87d366aa 100644
--- a/lib/primer/open_project/forms/dsl/autocompleter_input.rb
+++ b/lib/primer/open_project/forms/dsl/autocompleter_input.rb
@@ -18,9 +18,8 @@ def initialize(label:, value:, selected: false)
def to_h
{
- label:,
- value:,
- selected:
+ id: value,
+ name: label
}
end
end
diff --git a/spec/support/pages/admin/custom_actions/form.rb b/spec/support/pages/admin/custom_actions/form.rb
index 41054956c733..f700f0d551c7 100644
--- a/spec/support/pages/admin/custom_actions/form.rb
+++ b/spec/support/pages/admin/custom_actions/form.rb
@@ -118,7 +118,7 @@ def set_field_value(field, name, value)
if has_selector?(".form--selected-value--container", wait: 0)
find(".form--selected-value--container").click
autocomplete = true
- elsif has_selector?(".autocomplete-select-decoration--wrapper", wait: 0)
+ elsif has_selector?("op-autocompleter", wait: 0)
autocomplete = true
end