diff --git a/Gemfile.lock b/Gemfile.lock index bbf3dcc197..bedb36e2a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - avo (3.0.6) + avo (3.0.7) actionview (>= 6.1) active_link_to activerecord (>= 6.1) diff --git a/app/components/avo/field_wrapper_component.rb b/app/components/avo/field_wrapper_component.rb index fe1a4a8163..f7088feb34 100644 --- a/app/components/avo/field_wrapper_component.rb +++ b/app/components/avo/field_wrapper_component.rb @@ -36,6 +36,7 @@ def initialize( @full_width = full_width @label = label @resource = resource + @action = field.action @short = short @stacked = stacked @style = style @@ -73,9 +74,11 @@ def data # Add the built-in stimulus integration data tags. if @resource.present? - @resource.get_stimulus_controllers.split(" ").each do |controller| - attributes["#{controller}-target"] = "#{@field.id.to_s.underscore}_#{@field.type.to_s.underscore}_wrapper".camelize(:lower) - end + add_stimulus_attributes_for(@resource, attributes) + end + + if @action.present? + add_stimulus_attributes_for(@action, attributes) end # Fetch the data attributes off the html option @@ -109,4 +112,12 @@ def short? def full_width? @full_width end + + private + + def add_stimulus_attributes_for(entity, attributes) + entity.get_stimulus_controllers.split(" ").each do |controller| + attributes["#{controller}-target"] = "#{@field.id.to_s.underscore}_#{@field.type.to_s.underscore}_wrapper".camelize(:lower) + end + end end diff --git a/app/controllers/avo/actions_controller.rb b/app/controllers/avo/actions_controller.rb index 3f4184f291..bdec1d4047 100644 --- a/app/controllers/avo/actions_controller.rb +++ b/app/controllers/avo/actions_controller.rb @@ -26,7 +26,8 @@ def handle fields: action_params[:fields].except(:avo_resource_ids, :avo_selected_query), current_user: _current_user, resource: resource, - query: decrypted_query || @resource.find_record(resource_ids, params: params) + query: decrypted_query || + (resource_ids.any? ? @resource.find_record(resource_ids, params: params) : []) ) respond performed_action.response diff --git a/app/views/avo/actions/show.html.erb b/app/views/avo/actions/show.html.erb index 256ea5133c..ac7807fe37 100644 --- a/app/views/avo/actions/show.html.erb +++ b/app/views/avo/actions/show.html.erb @@ -1,6 +1,6 @@ <%= turbo_frame_tag "actions_show" do %>
" data-no-confirmation="<%= @action.no_confirmation %>" data-action-target="controllerDiv" data-resource-name="<%= @resource.model_key %>" @@ -28,7 +28,7 @@
<% @action.get_fields.each_with_index do |field, index| %> <%= render field - .hydrate(resource: @resource, record: @resource.record, user: @resource.user, view: @view) + .hydrate(resource: @resource, record: @resource.record, user: @resource.user, view: @view, action: @action) .component_for_view(@view) .new(field: field, resource: @resource, index: index, form: form, compact: true) %> diff --git a/lib/avo/base_action.rb b/lib/avo/base_action.rb index 2996a3116a..cd64140dcd 100644 --- a/lib/avo/base_action.rb +++ b/lib/avo/base_action.rb @@ -1,6 +1,7 @@ module Avo class BaseAction include Avo::Concerns::HasItems + include Avo::Concerns::HasActionStimulusControllers class_attribute :name, default: nil class_attribute :message diff --git a/lib/avo/base_resource.rb b/lib/avo/base_resource.rb index db2de57530..6fd9a93b24 100644 --- a/lib/avo/base_resource.rb +++ b/lib/avo/base_resource.rb @@ -6,7 +6,7 @@ class BaseResource include Avo::Concerns::HasItems include Avo::Concerns::CanReplaceItems include Avo::Concerns::HasControls - include Avo::Concerns::HasStimulusControllers + include Avo::Concerns::HasResourceStimulusControllers include Avo::Concerns::ModelClassConstantized include Avo::Concerns::HasDescription include Avo::Concerns::HasHelpers diff --git a/lib/avo/concerns/has_action_stimulus_controllers.rb b/lib/avo/concerns/has_action_stimulus_controllers.rb new file mode 100644 index 0000000000..029b123cb9 --- /dev/null +++ b/lib/avo/concerns/has_action_stimulus_controllers.rb @@ -0,0 +1,15 @@ +module Avo + module Concerns + module HasActionStimulusControllers + extend ActiveSupport::Concern + + included do + class_attribute :stimulus_controllers, default: "" + end + + def get_stimulus_controllers + self.class.stimulus_controllers + end + end + end +end diff --git a/lib/avo/concerns/has_stimulus_controllers.rb b/lib/avo/concerns/has_resource_stimulus_controllers.rb similarity index 95% rename from lib/avo/concerns/has_stimulus_controllers.rb rename to lib/avo/concerns/has_resource_stimulus_controllers.rb index 89847da804..f08ac0cfc4 100644 --- a/lib/avo/concerns/has_stimulus_controllers.rb +++ b/lib/avo/concerns/has_resource_stimulus_controllers.rb @@ -1,6 +1,6 @@ module Avo module Concerns - module HasStimulusControllers + module HasResourceStimulusControllers extend ActiveSupport::Concern included do diff --git a/lib/avo/fields/base_field.rb b/lib/avo/fields/base_field.rb index a2ce2e90c3..43f80c91f5 100644 --- a/lib/avo/fields/base_field.rb +++ b/lib/avo/fields/base_field.rb @@ -80,6 +80,7 @@ def initialize(id, **args, &block) @stacked = args[:stacked] || nil @for_presentation_only = args[:for_presentation_only] || false @resource = args[:resource] + @action = args[:action] @components = args[:components] || {} @args = args diff --git a/lib/avo/fields/concerns/has_html_attributes.rb b/lib/avo/fields/concerns/has_html_attributes.rb index 796fbcc147..6065e49ffc 100644 --- a/lib/avo/fields/concerns/has_html_attributes.rb +++ b/lib/avo/fields/concerns/has_html_attributes.rb @@ -30,7 +30,10 @@ def get_html(name = nil, element:, view:) default_attribute_value name end - add_default_data_attributes attributes, name, element, view + add_action_data_attributes(attributes, name, element) + add_resource_data_attributes(attributes, name, element, view) + + attributes end private @@ -50,18 +53,17 @@ def default_attribute_value(name) name == :data ? {} : "" end - def add_default_data_attributes(attributes, name, element, view) - if !attributes.nil? && name == :data && element == :input && view.in?([:edit, :new]) && resource.present? && resource.respond_to?(:get_stimulus_controllers) - extra_attributes = resource.get_stimulus_controllers - .split(" ") - .map do |controller| - [:"#{controller}-target", "#{id.to_s.underscore}_#{type.to_s.underscore}_input".camelize(:lower)] - end - .to_h + def add_action_data_attributes(attributes, name, element) + if can_add_stimulus_attributes_for?(action, attributes, name, element) + attributes.merge!(stimulus_attributes_for(action)) + end + end - attributes.merge extra_attributes - else - attributes + def add_resource_data_attributes(attributes, name, element, view) + if can_add_stimulus_attributes_for?(resource, attributes, name, element) && view.in?([:edit, :new]) + resource_stimulus_attributes = stimulus_attributes_for(resource) + + attributes.merge!(resource_stimulus_attributes) end end @@ -106,6 +108,19 @@ def merge_values_as(as: :array, values: []) result if result.present? end + + def can_add_stimulus_attributes_for?(entity, attributes, name, element) + !attributes.nil? && name == :data && element == :input && entity.present? && entity.respond_to?(:get_stimulus_controllers) + end + + def stimulus_attributes_for(entity) + entity.get_stimulus_controllers + .split(" ") + .map do |controller| + [:"#{controller}-target", "#{id.to_s.underscore}_#{type.to_s.underscore}_input".camelize(:lower)] + end + .to_h + end end end end diff --git a/spec/dummy/app/avo/actions/show_current_time.rb b/spec/dummy/app/avo/actions/show_current_time.rb new file mode 100644 index 0000000000..2b6b510a7e --- /dev/null +++ b/spec/dummy/app/avo/actions/show_current_time.rb @@ -0,0 +1,39 @@ +class Avo::Actions::ShowCurrentTime < Avo::BaseAction + self.name = "Show current time" + self.standalone = true + self.stimulus_controllers = "city-in-country" + + def fields + field :country, + as: :select, + name: "Country", + options: Course.countries.map { |country| [country, country] }.prepend(["-", nil]).to_h, + html: { + edit: { + input: { + data: { + action: "city-in-country#onCountryChange" + } + } + } + } + field :city, + as: :select, + name: "City", + options: Course.cities.values.flatten.map { |city| [city, city] }.to_h, + display_value: false + end + + def handle(**args) + city = args.dig(:fields, :city) + timezone_id = Course.timezones.find { |_, cities| cities.include?(city) }&.first + + if timezone_id + formatted_current_time = TZInfo::Timezone.get(timezone_id).now.strftime('%H:%M:%S') + + succeed "In #{city} it's now #{formatted_current_time}." + else + warn "No city chosen" + end + end +end diff --git a/spec/dummy/app/avo/resources/course.rb b/spec/dummy/app/avo/resources/course.rb index 6feca9abb3..19c5cd9b57 100644 --- a/spec/dummy/app/avo/resources/course.rb +++ b/spec/dummy/app/avo/resources/course.rb @@ -3,7 +3,7 @@ class Avo::Resources::Course < Avo::BaseResource query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], m: "or").result(distinct: false) } } self.keep_filters_panel_open = true - self.stimulus_controllers = "course-resource toggle-fields" + self.stimulus_controllers = "city-in-country toggle-fields" def fields @@ -61,7 +61,7 @@ def fields edit: { input: { data: { - action: "course-resource#onCountryChange" + action: "city-in-country#onCountryChange" } } } @@ -79,4 +79,8 @@ def filters filter Avo::Filters::CourseCountryFilter filter Avo::Filters::CourseCityFilter end + + def actions + action Avo::Actions::ShowCurrentTime + end end diff --git a/spec/dummy/app/javascript/avo.custom.js b/spec/dummy/app/javascript/avo.custom.js index c1e75e336a..6eb2cbf039 100644 --- a/spec/dummy/app/javascript/avo.custom.js +++ b/spec/dummy/app/javascript/avo.custom.js @@ -1,5 +1,5 @@ import { Application } from '@hotwired/stimulus' -import CourseController from './avo_custom/course_resource_controller' +import CityInCountryController from './avo_custom/city_in_country_controller' import NestedForm from 'stimulus-rails-nested-form' // Use you own stimulus install @@ -9,7 +9,7 @@ const application = Application.start() // Configure Stimulus development experience application.debug = window?.localStorage.getItem('avo.debug') -application.register('course-resource', CourseController) +application.register('city-in-country', CityInCountryController) application.register('nested-form', NestedForm) // eslint-disable-next-line no-console diff --git a/spec/dummy/app/javascript/avo_custom/course_resource_controller.js b/spec/dummy/app/javascript/avo_custom/city_in_country_controller.js similarity index 90% rename from spec/dummy/app/javascript/avo_custom/course_resource_controller.js rename to spec/dummy/app/javascript/avo_custom/city_in_country_controller.js index ed90f0b72e..c1224dad67 100644 --- a/spec/dummy/app/javascript/avo_custom/course_resource_controller.js +++ b/spec/dummy/app/javascript/avo_custom/city_in_country_controller.js @@ -13,7 +13,7 @@ export default class extends Controller { static initialValue get placeholder() { - return this.citySelectInputTarget.ariaPlaceholder + return this.citySelectInputTarget.getAttribute('aria-placeholder') } set loading(isLoading) { @@ -34,13 +34,10 @@ export default class extends Controller { } async connect() { - // Add the controller functionality only on forms - if (['edit', 'new'].includes(this.viewValue)) { - this.captureTheInitialValue() + this.captureTheInitialValue() - // Trigger the change on load - await this.onCountryChange() - } + // Trigger the change on load + await this.onCountryChange() } // Read the country select. diff --git a/spec/dummy/app/models/course.rb b/spec/dummy/app/models/course.rb index 915b806e17..0751b95844 100644 --- a/spec/dummy/app/models/course.rb +++ b/spec/dummy/app/models/course.rb @@ -47,6 +47,16 @@ def self.cities } end + def self.timezones + { + "America/New_York" => ["New York", "Boston", "Philadelphia"], + "America/Los_Angeles" => ["Los Angeles", "San Francisco"], + "Asia/Tokyo" => ["Tokyo", "Osaka", "Kyoto", "Hiroshima", "Yokohama", "Nagoya", "Kobe"], + "Europe/Madrid" => ["Madrid", "Valencia", "Barcelona"], + "Asia/Bangkok" => ["Chiang Mai", "Bangkok", "Phuket"] + } + end + def self.ransackable_attributes(auth_object = nil) ["city", "country", "created_at", "id", "name", "skills", "starting_at", "updated_at"] end diff --git a/spec/system/avo/stimulus_spec.rb b/spec/system/avo/stimulus_spec.rb index f4d470264f..86938fdf01 100644 --- a/spec/system/avo/stimulus_spec.rb +++ b/spec/system/avo/stimulus_spec.rb @@ -15,7 +15,7 @@ end end - describe "course_controller#onCountryChange" do + describe "city-in-country#onCountryChange" do it "changes the cities" do visit "/admin/resources/courses/#{course.id}/edit" @@ -38,4 +38,36 @@ expect(page).to have_select "course_city", selected: "Kyoto", options: ["Choose an option", "Tokyo", "Osaka", "Kyoto", "Hiroshima", "Yokohama", "Nagoya", "Kobe"] end end + + describe "city-in-country#onCountryChange (attached to ShowCurrentTime Action)" do + it "filters cities based on a given country" do + visit "/admin/resources/courses" + + click_on "Actions" + click_on "Show current time" + + expect(page).to have_css "[data-field-id='country']" + expect(page).to have_css "[data-field-id='city']" + expect(page).to have_select "fields_country" + + select "Spain", from: "fields_country" + + expect(page).to have_select "fields_country", selected: "Spain" + expect(page).to have_select "fields_city", selected: "Choose an option", options: ["Choose an option", "Madrid", "Valencia", "Barcelona"] + + select "Thailand", from: "fields_country" + + expect(page).to have_select "fields_country", selected: "Thailand" + expect(page).to have_select "fields_city", selected: "Choose an option", options: ["Choose an option", "Chiang Mai", "Bangkok", "Phuket"] + + select "Japan", from: "fields_country" + expect(page).to have_select "fields_country", selected: "Japan" + select "Kyoto", from: "fields_city" + expect(page).to have_select "fields_city", selected: "Kyoto", options: ["Choose an option", "Tokyo", "Osaka", "Kyoto", "Hiroshima", "Yokohama", "Nagoya", "Kobe"] + + click_on "Run" + + expect(page).to have_text(/In Kyoto it's now \d\d:\d\d:\d\d./) + end +end end