Skip to content

Commit

Permalink
feature: resource custom components (#3176)
Browse files Browse the repository at this point in the history
* step 1: use string as identifier for actual components

* step 2: grid view

* step3: tests

* separate tests

* resource table component

* map component

* add TableRowComponent

* lint

* lint

* rename name

* end rename
  • Loading branch information
Paul-Bob authored Aug 23, 2024
1 parent 792390d commit 3b706ce
Show file tree
Hide file tree
Showing 29 changed files with 216 additions and 76 deletions.
2 changes: 1 addition & 1 deletion app/components/avo/index/resource_grid_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
} do %>
<% @resources.each_with_index do |resource, index| %>
<% cache_if Avo.configuration.cache_resources_on_index_view, resource.cache_hash(@parent_record) do %>
<%= render(Avo::Index::GridItemComponent.new(resource: resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, actions: actions)) %>
<%= render(resource.resolve_component(Avo::Index::GridItemComponent).new(resource: resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, actions: actions)) %>
<% end %>
<% end %>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion app/components/avo/index/resource_map_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</div>
<% if render_table? %>
<div class="overflow-auto <%= table_component_order_class %>">
<%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
<%= render(@resource.resolve_component(Avo::Index::ResourceTableComponent).new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
</div>
<% end %>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/avo/index/resource_table_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def generate_table_row_components
header_fields.concat row_fields

# Create a TableRowComponent instance for the resource and add it to @table_row_components
table_row_components << Avo::Index::TableRowComponent.new(
table_row_components << resource.resolve_component(Avo::Index::TableRowComponent).new(
resource: resource,
fields: row_fields,
reflection: @reflection,
Expand Down
2 changes: 1 addition & 1 deletion app/components/avo/index/table_row_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Avo::Index::TableRowComponent < Avo::BaseComponent
prop :parent_resource, _Nilable(Avo::BaseResource)
prop :actions, _Nilable(_Array(Avo::BaseAction))
prop :fields, _Nilable(_Array(Avo::Fields::BaseField))
prop :header_fields, _Nilable(_Array(Avo::Fields::BaseField))
prop :header_fields, _Nilable(_Array(String))

def resource_controls_component
Avo::Index::ResourceControlsComponent.new(
Expand Down
4 changes: 2 additions & 2 deletions app/components/avo/views/resource_index_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
<% if view_type.to_sym == :table %>
<% if @resources.present? %>
<div class="w-full relative flex-1 flex mt-0">
<%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query, actions: @actions)) %>
<%= render(@resource.resolve_component(Avo::Index::ResourceTableComponent).new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query, actions: @actions)) %>
</div>
<% else %>
<%= helpers.empty_state by_association: params[:related_name].present?, view_type: view_type, add_background: true %>
Expand All @@ -70,7 +70,7 @@
<% if view_type.to_sym == :map %>
<% if @resources.present? %>
<div>
<%= render(Avo::Index::ResourceMapComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
<%= render(@resource.resolve_component(Avo::Index::ResourceMapComponent).new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
</div>
<% else %>
<%= helpers.empty_state by_association: params[:related_name].present?, view_type: view_type, add_background: true %>
Expand Down
16 changes: 6 additions & 10 deletions app/controllers/avo/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -581,18 +581,14 @@ def pagy_query
# Set the view component for the current view
# It will try to use the custom component if it's set, otherwise it will use the default one
def set_component_for(view, fallback_view: nil)
# Fetch the components from the resource
components = Avo::ExecutionContext.new(
target: @resource.components,
resource: @resource,
record: @record,
view: @view
).handle
default_component = "Avo::Views::Resource#{(fallback_view || view).to_s.classify}Component"

# Search for the custom component by key and by class name:
custom_component = @resource.custom_components.dig(:"resource_#{view}_component") ||
@resource.custom_components.dig(default_component)

# If the component is not set, use the default one
if (custom_component = components.dig(:"resource_#{view}_component")).nil?
return @component = "Avo::Views::Resource#{(fallback_view || view).to_s.classify}Component".constantize
end
return @component = default_component.constantize if custom_component.nil?

# If the component is set, try to use it
@component = custom_component.to_s.safe_constantize
Expand Down
13 changes: 13 additions & 0 deletions lib/avo/resources/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,19 @@ def record_param
@record_param ||= @record.persisted? ? @record.to_param : nil
end

def custom_components
@custom_components ||= Avo::ExecutionContext.new(
target: components,
resource: self,
record: @record,
view: @view
).handle.with_indifferent_access
end

def resolve_component(original_component)
custom_components.dig(original_component.to_s)&.to_s&.safe_constantize || original_component
end

private

def flatten_keys(array)
Expand Down
12 changes: 0 additions & 12 deletions spec/components/avo/index/field_wrapper_component_spec.rb

This file was deleted.

12 changes: 0 additions & 12 deletions spec/components/avo/index/grid_item_component_spec.rb

This file was deleted.

12 changes: 0 additions & 12 deletions spec/components/avo/index/resource_grid_component_spec.rb

This file was deleted.

12 changes: 0 additions & 12 deletions spec/components/avo/index/resource_table_component_spec.rb

This file was deleted.

12 changes: 0 additions & 12 deletions spec/components/avo/index/table_row_component_spec.rb

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="flex flex-col">
Custom grid item component here!
<%= render(Avo::Index::GridItemComponent.new(**@args)) %>
</div>
7 changes: 7 additions & 0 deletions spec/dummy/app/components/avo/for_test/grid_item_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Avo::ForTest::GridItemComponent < Avo::BaseComponent
def initialize(**args)
@args = args
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="flex flex-col">
Custom edit component here!
<%= render(Avo::Views::ResourceEditComponent.new(**@args)) %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Avo::ForTest::ResourceEditComponent < Avo::ResourceComponent
def initialize(**args)
@args = args
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="flex flex-col">
Custom index component here!
<%= render(Avo::Views::ResourceIndexComponent.new(**@args)) %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Avo::ForTest::ResourceIndexComponent < Avo::ResourceComponent
def initialize(**args)
@args = args
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="flex flex-col">
Custom map component here!
<%= render(Avo::Index::ResourceMapComponent.new(**@args)) %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Avo::ForTest::ResourceMapComponent < Avo::BaseComponent
def initialize(**args)
@args = args
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="flex flex-col">
Custom new component here!
<%= render(Avo::Views::ResourceEditComponent.new(**@args)) %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Avo::ForTest::ResourceNewComponent < Avo::ResourceComponent
def initialize(**args)
@args = args
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="flex flex-col">
Custom show component here!
<%= render(Avo::Views::ResourceShowComponent.new(**@args)) %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Avo::ForTest::ResourceShowComponent < Avo::ResourceComponent
def initialize(**args)
@args = args
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="flex flex-col">
Resource table component here!
<%= render(Avo::Index::ResourceTableComponent.new(**@args)) %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Avo::ForTest::ResourceTableComponent < Avo::BaseComponent
def initialize(**args)
@args = args
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="flex flex-col">
Table row component here!
<%= render(Avo::Index::TableRowComponent.new(**args)) %>
</div>
12 changes: 12 additions & 0 deletions spec/dummy/app/components/avo/for_test/table_row_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

class Avo::ForTest::TableRowComponent < Avo::Index::TableRowComponent
def initialize(**args)
@args = args
end

def args
@args.merge(header_fields: @header_fields)
end
end
# TODO: document that need to inherit from < Avo::Index::TableRowComponent and need to pass @header_files... and explain why
98 changes: 98 additions & 0 deletions spec/features/avo/resource_custom_components_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "Custom components", type: :feature do
describe "symbol" do
before do
Avo::Resources::User.components = {
resource_index_component: Avo::ForTest::ResourceIndexComponent,
resource_show_component: "Avo::ForTest::ResourceShowComponent",
resource_edit_component: "Avo::ForTest::ResourceEditComponent",
resource_new_component: Avo::ForTest::ResourceNewComponent
}
end

it "index" do
visit avo.resources_users_path

expect(page).to have_text("Custom index component here!")
end

it "show" do
visit avo.resources_user_path(admin)

expect(page).to have_text("Custom show component here!")
end

it "new" do
visit avo.new_resources_user_path

expect(page).to have_text("Custom new component here!")
end

it "edit" do
visit avo.edit_resources_user_path(admin)

expect(page).to have_text("Custom edit component here!")
end
end

describe "class" do
before do
Avo::Resources::User.components = {
"Avo::Views::ResourceIndexComponent": Avo::ForTest::ResourceIndexComponent,
"Avo::Views::ResourceShowComponent": "Avo::ForTest::ResourceShowComponent",
"Avo::Views::ResourceEditComponent": "Avo::ForTest::ResourceEditComponent",
"Avo::Index::GridItemComponent": "Avo::ForTest::GridItemComponent",
"Avo::Index::ResourceTableComponent": "Avo::ForTest::ResourceTableComponent",
"Avo::Index::TableRowComponent": "Avo::ForTest::TableRowComponent"
}
end

it "index and table" do
visit avo.resources_users_path

expect(page).to have_text("Custom index component here!")
expect(page).to have_text("Resource table component here!")
expect(page).to have_text("Table row component here!")
end

it "grid item component" do
visit avo.resources_users_path(view_type: :grid)

expect(page).to have_text("Custom grid item component here!")
end

it "show" do
visit avo.resources_user_path(admin)

expect(page).to have_text("Custom show component here!")
end

# Impossible to specify for new, only for edit.
it "new" do
visit avo.new_resources_user_path

expect(page).to have_text("Custom edit component here!")
end

it "edit" do
visit avo.edit_resources_user_path(admin)

expect(page).to have_text("Custom edit component here!")
end

it "map" do
Avo::Resources::City.components = {
"Avo::Index::ResourceMapComponent": "Avo::ForTest::ResourceMapComponent"
}

City.create!

visit avo.resources_cities_path(view_type: :map)

expect(page).to have_text("Custom map component here!")
end
end
end

0 comments on commit 3b706ce

Please sign in to comment.