diff --git a/app/components/avo/fields/area_field/edit_component.html.erb b/app/components/avo/fields/area_field/edit_component.html.erb index d5cc6c77a6..48571b5bde 100644 --- a/app/components/avo/fields/area_field/edit_component.html.erb +++ b/app/components/avo/fields/area_field/edit_component.html.erb @@ -1,5 +1,6 @@ <%= field_wrapper **field_wrapper_args do %> <%= @form.text_field @field.id, + value: @field.value, class: classes("w-full"), value: field.value.to_s, placeholder: @field.placeholder, diff --git a/app/components/avo/fields/boolean_field/edit_component.html.erb b/app/components/avo/fields/boolean_field/edit_component.html.erb index f80de21115..5910c47427 100644 --- a/app/components/avo/fields/boolean_field/edit_component.html.erb +++ b/app/components/avo/fields/boolean_field/edit_component.html.erb @@ -1,6 +1,7 @@ <%= field_wrapper **field_wrapper_args, dash_if_blank: false do %>
<%= @form.check_box @field.id, + value: @field.value, checked: @field.value, class: "text-lg h-4 w-4 checked:bg-primary-400 focus:checked:!bg-primary-400 #{@field.get_html(:classes, view: view, element: :input)}", data: @field.get_html(:data, view: view, element: :input), diff --git a/app/components/avo/fields/code_field/edit_component.html.erb b/app/components/avo/fields/code_field/edit_component.html.erb index eb9184f99d..39823519af 100644 --- a/app/components/avo/fields/code_field/edit_component.html.erb +++ b/app/components/avo/fields/code_field/edit_component.html.erb @@ -1,6 +1,7 @@ <%= field_wrapper **field_wrapper_args, full_width: true do %>
<%= @form.text_area @field.id, + value: @field.value, class: classes("w-full"), data: { 'code-field-target': 'element', diff --git a/app/components/avo/fields/country_field/edit_component.html.erb b/app/components/avo/fields/country_field/edit_component.html.erb index e17dc3c386..eceb336c1b 100644 --- a/app/components/avo/fields/country_field/edit_component.html.erb +++ b/app/components/avo/fields/country_field/edit_component.html.erb @@ -1,5 +1,6 @@ <%= field_wrapper **field_wrapper_args do %> <%= @form.select @field.id, @field.select_options, { + value: @field.value, selected: @field.value, include_blank: @field.include_blank }, diff --git a/app/components/avo/fields/markdown_field/edit_component.html.erb b/app/components/avo/fields/markdown_field/edit_component.html.erb index f3ebb4eb13..3985405f60 100644 --- a/app/components/avo/fields/markdown_field/edit_component.html.erb +++ b/app/components/avo/fields/markdown_field/edit_component.html.erb @@ -1,6 +1,7 @@ <%= field_wrapper **field_wrapper_args, full_width: true do %>
<%= @form.text_area @field.id, + value: @field.value, class: classes("w-full js-has-easy-mde-editor"), data: { view: view, diff --git a/app/components/avo/fields/number_field/edit_component.html.erb b/app/components/avo/fields/number_field/edit_component.html.erb index 0a23e7131d..173b997214 100644 --- a/app/components/avo/fields/number_field/edit_component.html.erb +++ b/app/components/avo/fields/number_field/edit_component.html.erb @@ -1,5 +1,6 @@ <%= field_wrapper **field_wrapper_args do %> <%= @form.number_field @field.id, + value: @field.value, class: classes("w-full"), data: @field.get_html(:data, view: view, element: :input), disabled: disabled?, diff --git a/app/components/avo/fields/password_field/edit_component.html.erb b/app/components/avo/fields/password_field/edit_component.html.erb index 2b41112ed4..d333690853 100644 --- a/app/components/avo/fields/password_field/edit_component.html.erb +++ b/app/components/avo/fields/password_field/edit_component.html.erb @@ -1,5 +1,6 @@ <%= field_wrapper **field_wrapper_args do %> <%= @form.password_field @field.id, + value: @field.value, class: classes("w-full"), data: @field.get_html(:data, view: view, element: :input), disabled: disabled?, diff --git a/app/components/avo/fields/progress_bar_field/edit_component.html.erb b/app/components/avo/fields/progress_bar_field/edit_component.html.erb index 59bae9bd99..3caaba3183 100644 --- a/app/components/avo/fields/progress_bar_field/edit_component.html.erb +++ b/app/components/avo/fields/progress_bar_field/edit_component.html.erb @@ -6,6 +6,7 @@
<% end %> <%= @form.range_field @field.id, + value: @field.value, class: "w-full #{@field.get_html(:classes, view: view, element: :input)}", data: { action: "input->progress-bar-field#update", diff --git a/app/components/avo/fields/status_field/edit_component.html.erb b/app/components/avo/fields/status_field/edit_component.html.erb index 745443558f..72853f5ce7 100644 --- a/app/components/avo/fields/status_field/edit_component.html.erb +++ b/app/components/avo/fields/status_field/edit_component.html.erb @@ -5,6 +5,6 @@ disabled: disabled?, placeholder: @field.placeholder, style: @field.get_html(:style, view: view, element: :input), - value: @resource.model.present? ? @resource.model[@field.id] : @field.value + value: @field.value %> <% end %> diff --git a/app/components/avo/fields/text_field/edit_component.html.erb b/app/components/avo/fields/text_field/edit_component.html.erb index a74713606d..0283bbd7c5 100644 --- a/app/components/avo/fields/text_field/edit_component.html.erb +++ b/app/components/avo/fields/text_field/edit_component.html.erb @@ -1,11 +1,11 @@ <%= field_wrapper **field_wrapper_args do %> <%= form.text_field @field.id, + value: @field.value, class: classes("w-full"), data: @field.get_html(:data, view: view, element: :input), disabled: disabled?, placeholder: @field.placeholder, style: @field.get_html(:style, view: view, element: :input), - # value: @field.value, multiple: multiple, autocomplete: @field.autocomplete %> diff --git a/app/components/avo/fields/textarea_field/edit_component.html.erb b/app/components/avo/fields/textarea_field/edit_component.html.erb index 45ebdc2298..88f9d279b5 100644 --- a/app/components/avo/fields/textarea_field/edit_component.html.erb +++ b/app/components/avo/fields/textarea_field/edit_component.html.erb @@ -1,5 +1,6 @@ <%= field_wrapper **field_wrapper_args do %> <%= @form.text_area @field.id, + value: @field.value, class: classes("w-full"), data: @field.get_html(:data, view: view, element: :input), disabled: disabled?, diff --git a/app/components/avo/fields/trix_field/edit_component.html.erb b/app/components/avo/fields/trix_field/edit_component.html.erb index d106cfef55..8fe18ef815 100644 --- a/app/components/avo/fields/trix_field/edit_component.html.erb +++ b/app/components/avo/fields/trix_field/edit_component.html.erb @@ -23,6 +23,7 @@ <%= sanitize @field.value.to_s %> <% end %> <%= @form.text_area @field.id, + value: @field.value, class: classes("w-full hidden"), data: @field.get_html(:data, view: view, element: :input), disabled: disabled?, diff --git a/app/components/avo/views/resource_edit_component.html.erb b/app/components/avo/views/resource_edit_component.html.erb index 23add17c99..6f13568b2d 100644 --- a/app/components/avo/views/resource_edit_component.html.erb +++ b/app/components/avo/views/resource_edit_component.html.erb @@ -7,7 +7,7 @@ selected_resources: [@resource.model.id], **@resource.stimulus_data_attributes } do %> - <%= form_with model: @resource.model, + <%= form_with model: @resource.record, scope: @resource.form_scope, url: form_url, method: form_method, diff --git a/lib/avo/base_resource.rb b/lib/avo/base_resource.rb index bbfc86df6d..27e1b7cbff 100644 --- a/lib/avo/base_resource.rb +++ b/lib/avo/base_resource.rb @@ -151,7 +151,8 @@ def initialize def record @model end - alias :model :record + alias_method :model, :record + def hydrate(model: nil, view: nil, user: nil, params: nil) @view = view if view.present? @@ -307,9 +308,9 @@ def attachment_fields end end - def fill_model(model, params, extra_params: []) - # Map the received params to their actual fields - fields_by_database_id = get_field_definitions + # Map the received params to their actual fields. + def fields_by_database_id + get_field_definitions .reject do |field| field.computed end @@ -317,7 +318,9 @@ def fill_model(model, params, extra_params: []) [field.database_id.to_s, field] end .to_h + end + def fill_model(model, params, extra_params: []) # Write the field values params.each do |key, value| field = fields_by_database_id[key] diff --git a/lib/avo/execution_context.rb b/lib/avo/execution_context.rb new file mode 100644 index 0000000000..e082aefb20 --- /dev/null +++ b/lib/avo/execution_context.rb @@ -0,0 +1,43 @@ +module Avo + class ExecutionContext + attr_accessor :target, :context, :params, :view_context, :current_user, :request, :include, :main_app, :avo + + def initialize(**args) + # Extend the class with custom modules if required. + if args[:include].present? + args[:include].each do |mod| + self.class.send(:include, mod) + end + end + + # If you want this block to behave like a view you can delegate the missing methods to the view_context + # + # Ex: Avo::ExecutionContext.new(target: ..., delegate_missing_to: :view_context).handle + if args[:delegate_missing_to].present? + self.class.send(:delegate_missing_to, args[:delegate_missing_to]) + end + + # If target doesn't respond to call, we don't need to initialize the others attr_accessors. + return unless (@target = args[:target]).respond_to? :call + + args.except(:target).each do |key, value| + singleton_class.class_eval { attr_accessor key } + instance_variable_set("@#{key}", value) + end + + # Set defaults on not initialized accessors + @context ||= Avo::App.context + @params ||= Avo::App.params + @view_context ||= Avo::App.view_context + @current_user ||= Avo::App.current_user + @request ||= view_context.request + @main_app ||= view_context.main_app + @avo ||= view_context.avo + end + + # Return target if target is not callable, otherwise, execute target on this instance context + def handle + target.respond_to?(:call) ? instance_exec(&target) : target + end + end +end diff --git a/lib/avo/fields/base_field.rb b/lib/avo/fields/base_field.rb index 6441a8d64f..a6a6a68fb1 100644 --- a/lib/avo/fields/base_field.rb +++ b/lib/avo/fields/base_field.rb @@ -70,6 +70,7 @@ def initialize(id, **args, &block) @nullable = args[:nullable] || false @null_values = args[:null_values] || [nil, ""] @format_using = args[:format_using] || nil + @update_using = args[:update_using] || nil @placeholder = args[:placeholder] @autocomplete = args[:autocomplete] || nil @help = args[:help] || nil @@ -183,20 +184,31 @@ def value(property = nil) final_value = instance_exec(@model, @resource, @view, self, &block) end - # Run the value through resolver if present - final_value = instance_exec(final_value, &@format_using) if @format_using.present? - - final_value + if @format_using.present? + # Apply the changes in the + Avo::ExecutionContext.new(target: @format_using, model: model, key: property, value: final_value, resource: resource, view: view, field: self, delegate_missing_to: :view_context).handle + else + final_value + end end + # Fills the model with the received value on create and update actions. def fill_field(model, key, value, params) return model unless model.methods.include? key.to_sym - model.send("#{key}=", value) + if @update_using.present? + value = update_using(model, key, value, params) + end + + model.public_send("#{key}=", value) model end + def update_using(model, key, value, params) + Avo::ExecutionContext.new(target: @update_using, model: model, key: key, value: value, resource: resource, field: self).handle + end + # Try to see if the field has a different database ID than it's name def database_id foreign_key diff --git a/spec/dummy/app/avo/resources/city_resource.rb b/spec/dummy/app/avo/resources/city_resource.rb index f6846e72ae..083cc28c61 100644 --- a/spec/dummy/app/avo/resources/city_resource.rb +++ b/spec/dummy/app/avo/resources/city_resource.rb @@ -9,7 +9,7 @@ class CityResource < Avo::BaseResource self.search_result_path = -> { avo.resources_city_path record, custom: "yup" } - self.extra_params = [:fish_type, :something_else, properties: [], information: [:name, :history]] + self.extra_params = [city: [:name, :metadata, :coordinates, :city_center_area, :description, :population, :is_capital, :image_url, :tiny_description, :status, features: {}, metadata: {}]] self.default_view_type = :map self.map_view = { mapkick_options: { @@ -43,12 +43,23 @@ class CityResource < Avo::BaseResource color: "#009099" } field :description, as: :trix, attachment_key: :description_file, visible: ->(resource:) { resource.params[:show_native_fields].blank? } + field :metadata, + as: :code, + format_using: -> { + if view == :edit + JSON.generate(value) + else + value + end + }, + update_using: -> do + ActiveSupport::JSON.decode(value) + end with_options hide_on: :forms do field :name, as: :text, help: "The name of your city" field :population, as: :number field :is_capital, as: :boolean field :features, as: :key_value - field :metadata, as: :code field :image_url, as: :external_image field :tiny_description, as: :markdown field :status, as: :badge, enum: ::City.statuses diff --git a/spec/dummy/app/avo/resources/comment_resource.rb b/spec/dummy/app/avo/resources/comment_resource.rb index 242b9d4fc1..9b25799b6d 100644 --- a/spec/dummy/app/avo/resources/comment_resource.rb +++ b/spec/dummy/app/avo/resources/comment_resource.rb @@ -10,7 +10,7 @@ class CommentResource < Avo::BaseResource self.after_update_path = :index field :id, as: :id - field :body, as: :textarea, format_using: ->(value) do + field :body, as: :textarea, format_using: -> do if view == :show content_tag(:div, style: "white-space: pre-line") { value } else diff --git a/spec/dummy/app/avo/resources/photo_comment_resource.rb b/spec/dummy/app/avo/resources/photo_comment_resource.rb index 346169b0d3..2b310fc27d 100644 --- a/spec/dummy/app/avo/resources/photo_comment_resource.rb +++ b/spec/dummy/app/avo/resources/photo_comment_resource.rb @@ -14,7 +14,7 @@ class PhotoCommentResource < Avo::BaseResource end field :id, as: :id - field :body, as: :textarea, format_using: ->(value) do + field :body, as: :textarea, format_using: -> do if view == :show content_tag(:div, style: "white-space: pre-line") { value } else diff --git a/spec/dummy/app/avo/resources/post_resource.rb b/spec/dummy/app/avo/resources/post_resource.rb index a6c2b13970..1437d57dd3 100644 --- a/spec/dummy/app/avo/resources/post_resource.rb +++ b/spec/dummy/app/avo/resources/post_resource.rb @@ -36,7 +36,7 @@ class PostResource < Avo::BaseResource enforce_suggestions: true, help: "The only allowed values here are `one`, `two`, and `three`" field :cover_photo, as: :file, is_image: true, as_avatar: :rounded, full_width: true, hide_on: [], accept: "image/*", display_filename: false - field :cover_photo, as: :external_image, name: "Cover photo", required: true, hide_on: :all, link_to_resource: true, as_avatar: :rounded, format_using: ->(value) { value.present? ? value&.url : nil } + field :cover_photo, as: :external_image, name: "Cover photo", required: true, hide_on: :all, link_to_resource: true, as_avatar: :rounded, format_using: -> { value.present? ? value&.url : nil } field :audio, as: :file, is_audio: true, accept: "audio/*" field :excerpt, as: :text, hide_on: :all, as_description: true do |model| extract_excerpt model.body diff --git a/spec/dummy/app/avo/resources/product_resource.rb b/spec/dummy/app/avo/resources/product_resource.rb index c97ce34cf6..e4523db310 100644 --- a/spec/dummy/app/avo/resources/product_resource.rb +++ b/spec/dummy/app/avo/resources/product_resource.rb @@ -31,7 +31,7 @@ class ProductResource < Avo::BaseResource } } title :title, as: :text - body :description, as: :textarea, format_using: ->(value) { + body :description, as: :textarea, format_using: -> { simple_format value } end diff --git a/spec/dummy/app/avo/resources/team_resource.rb b/spec/dummy/app/avo/resources/team_resource.rb index 224d0d00ee..48ac415787 100644 --- a/spec/dummy/app/avo/resources/team_resource.rb +++ b/spec/dummy/app/avo/resources/team_resource.rb @@ -31,7 +31,7 @@ class TeamResource < Avo::BaseResource rows: 5, readonly: false, hide_on: :index, - format_using: ->(value) { value.to_s.truncate 30 }, + format_using: -> { value.to_s.truncate 30 }, default: "This is a wonderful team!", nullable: true, null_values: ["0", "", "null", "nil"] diff --git a/spec/dummy/app/avo/resources/user_resource.rb b/spec/dummy/app/avo/resources/user_resource.rb index 06d8d791a0..b9065c6660 100644 --- a/spec/dummy/app/avo/resources/user_resource.rb +++ b/spec/dummy/app/avo/resources/user_resource.rb @@ -85,7 +85,7 @@ class UserResource < Avo::BaseResource hide_on: :edit do |model, resource, view, field| model.posts.to_a.size > 0 ? "yes" : "no" end - field :outside_link, as: :text, only_on: [:show], format_using: ->(url) { link_to("hey", url, target: "_blank") } do |model, *args| + field :outside_link, as: :text, only_on: [:show], format_using: -> { link_to("hey", value, target: "_blank") } do |model, *args| main_app.hey_url end end diff --git a/spec/dummy/app/avo/resources/z_post_resource.rb b/spec/dummy/app/avo/resources/z_post_resource.rb index 3ad098a98a..bc1b1b923e 100644 --- a/spec/dummy/app/avo/resources/z_post_resource.rb +++ b/spec/dummy/app/avo/resources/z_post_resource.rb @@ -34,7 +34,7 @@ class ZPostResource < Avo::BaseResource enforce_suggestions: true, help: "The only allowed values here are `one`, `two`, and `three`" field :cover_photo, as: :file, is_image: true, as_avatar: :rounded, full_width: true, hide_on: [], accept: "image/*" - field :cover_photo, as: :external_image, name: "Cover photo", required: true, hide_on: :all, link_to_resource: true, as_avatar: :rounded, format_using: ->(value) { value.present? ? value&.url : nil } + field :cover_photo, as: :external_image, name: "Cover photo", required: true, hide_on: :all, link_to_resource: true, as_avatar: :rounded, format_using: -> { value.present? ? value&.url : nil } field :audio, as: :file, is_audio: true, accept: "audio/*" field :excerpt, as: :text, hide_on: :all, as_description: true do |model| ActionView::Base.full_sanitizer.sanitize(model.body).truncate 130 diff --git a/spec/dummy/app/models/city.rb b/spec/dummy/app/models/city.rb index ee19fa1375..be16db0adf 100644 --- a/spec/dummy/app/models/city.rb +++ b/spec/dummy/app/models/city.rb @@ -38,4 +38,13 @@ def coordinates=(value) self.latitude = value.first self.longitude = value.last end + + # alternative to format_using and update_using + # def json_metadata + # ActiveSupport::JSON.encode(metadata) + # end + + # def json_metadata=(value) + # self.metadata = ActiveSupport::JSON.decode(value) + # end end diff --git a/spec/dummy/app/views/avo/resource_tools/_city_editor.html.erb b/spec/dummy/app/views/avo/resource_tools/_city_editor.html.erb index 3cc8686453..0fe7921df3 100644 --- a/spec/dummy/app/views/avo/resource_tools/_city_editor.html.erb +++ b/spec/dummy/app/views/avo/resource_tools/_city_editor.html.erb @@ -7,7 +7,6 @@ <%= avo_edit_field(:number, :population, form: form, help: "The population of the city.") %> <%= avo_edit_field(:is_capital, as: :boolean, form: form) %> <%= avo_edit_field(:features, as: :key_value, form: form) %> - <%= avo_show_field(:metadata, as: :code, form: form) %> <%= avo_show_field(:image_url, as: :external_image, form: form) do |model| 'https://images.unsplash.com/photo-1660061993776-098c0ee403ac?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY2MDMxMzc4NA&ixlib=rb-1.2.1&q=80&w=1080' end %> diff --git a/spec/features/avo/native_fields_spec.rb b/spec/features/avo/native_fields_spec.rb index 0e0e49e67d..d4b5ed2d94 100644 --- a/spec/features/avo/native_fields_spec.rb +++ b/spec/features/avo/native_fields_spec.rb @@ -10,7 +10,6 @@ expect(find_field('Population').value).to eq city.population.to_s expect(find_field('Is capital').value).to eq "1" expect(find_field('Features').value).to eq "\"#{city.features}\"" - expect(find_field('metadata', visible: false, disabled: true).value).to eq city.metadata expect(page).to have_css 'img[src="https://images.unsplash.com/photo-1660061993776-098c0ee403ac?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTY2MDMxMzc4NA&ixlib=rb-1.2.1&q=80&w=1080"]' expect(find_field('Image url').value).to eq city.image_url @@ -31,7 +30,6 @@ fill_in 'city[population]', with: 101 find('[name="city[is_capital]"]').set(false) fill_in 'city[features]', with: "{\"hey\": \"features\"}" - find_field('metadata', visible: false, disabled: true).set("{\"hey\": \"metadata\"}") click_on "Save" diff --git a/spec/system/avo/date_time_fields/date_time_eastern_spec.rb b/spec/system/avo/date_time_fields/date_time_eastern_spec.rb index 05e6aab820..9f80c9f97c 100644 --- a/spec/system/avo/date_time_fields/date_time_eastern_spec.rb +++ b/spec/system/avo/date_time_fields/date_time_eastern_spec.rb @@ -7,7 +7,7 @@ subject(:text_input) { find '[data-field-id="posted_at"] [data-controller="date-field"] input[type="text"]' } before do CommentResource.with_temporary_items do - field :body, as: :textarea, format_using: ->(value) do + field :body, as: :textarea, format_using: -> do if view == :show content_tag(:div, style: "white-space: pre-line") { value } else diff --git a/spec/system/avo/date_time_fields/date_time_utc_spec.rb b/spec/system/avo/date_time_fields/date_time_utc_spec.rb index 3c51529505..5389e33af6 100644 --- a/spec/system/avo/date_time_fields/date_time_utc_spec.rb +++ b/spec/system/avo/date_time_fields/date_time_utc_spec.rb @@ -8,7 +8,7 @@ before do CommentResource.with_temporary_items do - field :body, as: :textarea, format_using: ->(value) do + field :body, as: :textarea, format_using: -> do if view == :show content_tag(:div, style: "white-space: pre-line") { value } else diff --git a/spec/system/avo/date_time_fields/date_time_western_spec.rb b/spec/system/avo/date_time_fields/date_time_western_spec.rb index 03bb0dee88..3dfae8dfa6 100644 --- a/spec/system/avo/date_time_fields/date_time_western_spec.rb +++ b/spec/system/avo/date_time_fields/date_time_western_spec.rb @@ -8,7 +8,7 @@ before do CommentResource.with_temporary_items do - field :body, as: :textarea, format_using: ->(value) do + field :body, as: :textarea, format_using: -> do if view == :show content_tag(:div, style: "white-space: pre-line") { value } else