Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a belongs_to record from the associated record screen #1876

Merged
merged 20 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
26126ad
wip
sdcoffey Jul 29, 2023
d292661
move keep_modal_open view to shared partial
sdcoffey Jul 30, 2023
b9c7b9e
update base controller to show modal when creating via a belongs_to r…
sdcoffey Jul 30, 2023
b7754fd
WIP add create button to belongs_to edit field with return-handling c…
sdcoffey Jul 30, 2023
afc20c3
hook up panel back button from modal
sdcoffey Jul 30, 2023
c3a958b
pass params[:via_belongs_to_resource_class] through #create
sdcoffey Jul 30, 2023
c913b65
add WIP system spec for belongs_to cases
sdcoffey Jul 30, 2023
6b3effb
update specs to cover polymorphic and searchable polymorphic belongs_to
sdcoffey Jul 31, 2023
6336054
style and cleanup
sdcoffey Jul 31, 2023
f68c175
decompose long method in reload_belongs_to_controller
sdcoffey Jul 31, 2023
368533b
fixup! decompose long method in reload_belongs_to_controller
sdcoffey Jul 31, 2023
0454a34
remove change to .gitignore
sdcoffey Jul 31, 2023
25f91b6
remove usage of class_names
sdcoffey Aug 1, 2023
1e44d70
rename _keep_modal_open -> _flash_alerts
sdcoffey Aug 25, 2023
642136a
ensure validation errors are still shown after create failure
sdcoffey Aug 28, 2023
540a92b
add in_modal param to PanelComponent to allow better form display ins…
sdcoffey Aug 28, 2023
c94dc00
fixup! add in_modal param to PanelComponent to allow better form disp…
sdcoffey Aug 28, 2023
21042ec
use to_param/find_record instead of bare id
sdcoffey Aug 29, 2023
528432c
optionally render heading and content in modal
sdcoffey Aug 29, 2023
8427405
tweaks
adrianthedev Oct 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 118 additions & 96 deletions app/components/avo/fields/belongs_to_field/edit_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,115 +1,137 @@
<% if is_polymorphic? %>
<%
# Set the model keys so we can pass them over
model_keys = @field.types.map do |type|
resource = Avo::App.get_resource_by_model_name(type.to_s)
[type.to_s, resource.model_key]
end.to_h
%>
<div class="divide-y"
data-controller="belongs-to-field"
data-searchable="<%= @field.searchable %>"
data-association="<%= @field.id %>"
data-association-class="<%= @field&.target_resource&.model_class || nil %>"
>
<%= field_wrapper **field_wrapper_args, help: @field.polymorphic_help || '' do %>
<%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [::Avo::App.get_resource_by_model_name(type.to_s).name, type.to_s] },
{
value: @field.value,
include_blank: @field.placeholder,
},
{
class: classes("w-full"),
data: {
**@field.get_html(:data, view: view, element: :input),
action: "change->belongs-to-field#changeType #{field_html_action}",
'belongs-to-field-target': "select",
},
disabled: disabled
}
<div data-controller="reload-belongs-to-field"
data-action="turbo:before-stream-render@document->reload-belongs-to-field#beforeStreamRender"
data-reload-belongs-to-field-polymorphic-value="<%= is_polymorphic? %>"
data-reload-belongs-to-field-searchable-value="<%= @field.searchable %>"
data-reload-belongs-to-field-relation-name-value="<%= @field.id %>"
data-reload-belongs-to-field-target-name-value="<%= form.object_name %>[<%= @field.id_input_foreign_key %>]"
>
<% if is_polymorphic? %>
<%
# Set the model keys so we can pass them over
model_keys = @field.types.map do |type|
resource = Avo::App.get_resource_by_model_name(type.to_s)
[type.to_s, resource.model_key]
end.to_h
%>
<%
# If the select field is disabled, no value will be sent. It's how HTML works.
# Thus the extra hidden field to actually send the related id to the server.
if disabled %>
<%= @form.hidden_field @field.type_input_foreign_key %>
<div class="divide-y"
data-controller="belongs-to-field"
data-searchable="<%= @field.searchable %>"
data-association="<%= @field.id %>"
data-association-class="<%= @field&.target_resource&.model_class || nil %>"
>
<%= field_wrapper **field_wrapper_args, help: @field.polymorphic_help || '' do %>
<%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [::Avo::App.get_resource_by_model_name(type.to_s).name, type.to_s] },
{
value: @field.value,
include_blank: @field.placeholder,
},
{
class: classes("w-full"),
data: {
**@field.get_html(:data, view: view, element: :input),
action: "change->belongs-to-field#changeType #{field_html_action}",
'belongs-to-field-target': "select",
},
disabled: disabled
}
%>
<%
# If the select field is disabled, no value will be sent. It's how HTML works.
# Thus the extra hidden field to actually send the related id to the server.
if disabled %>
<%= @form.hidden_field @field.type_input_foreign_key %>
<% end %>
<% end %>
<% end %>
<% @field.types.each do |type| %>
<div class="hidden"
data-belongs-to-field-target="type"
data-type="<%= type %>"
>
<%= field_wrapper **field_wrapper_args, label: ::Avo::App.get_resource_by_model_name(type.to_s).name do %>
<% if @field.searchable %>
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
disabled: disabled,
field: @field,
foreign_key: @field.id_input_foreign_key,
model_key: model_keys[type.to_s],
polymorphic_record: polymorphic_record,
resource: @resource,
style: @field.get_html(:style, view: view, element: :input),
type: type,
classes: classes("w-full"),
view: view
<% @field.types.each do |type| %>
<div class="hidden"
data-belongs-to-field-target="type"
data-type="<%= type %>"
>
<%= field_wrapper **field_wrapper_args, label: ::Avo::App.get_resource_by_model_name(type.to_s).name do %>
<div class="flex flex-col gap-1">
<% if @field.searchable %>
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
disabled: disabled,
field: @field,
foreign_key: @field.id_input_foreign_key,
model_key: model_keys[type.to_s],
polymorphic_record: polymorphic_record,
resource: @resource,
style: @field.get_html(:style, view: view, element: :input),
type: type,
classes: classes("w-full"),
view: view
%>
<% else %>
<%= @form.select @field.id_input_foreign_key,
options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
{
value: @resource.model[@field.id_input_foreign_key].to_s,
include_blank: @field.placeholder,
},
{
class: classes("w-full"),
data: @field.get_html(:data, view: view, element: :input),
disabled: disabled
}
%>
<%
# If the select field is disabled, no value will be sent. It's how HTML works.
# Thus the extra hidden field to actually send the related id to the server.
if disabled %>
<%= @form.hidden_field @field.id_input_foreign_key %>
<% end %>
<% end %>
<% create_href = create_path(::Avo::App.get_resource_by_model_name(type.to_s)) %>
<% if !disabled && create_href.present? %>
<%= link_to t("avo.create_new_item", item: type.to_s.downcase),
create_href,
class: "text-sm"
%>
<% end %>
</div>
<% end %>
</div>
<% end %>
</div>
<% else %>
<%= field_wrapper **field_wrapper_args do %>
<div class="flex flex-col gap-1">
<% if @field.searchable %>
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
field: @field,
model_key: @field.target_resource&.model_key,
foreign_key: @field.id_input_foreign_key,
resource: @resource,
disabled: disabled,
classes: classes("w-full"),
view: view,
style: @field.get_html(:style, view: view, element: :input)
%>
<% else %>
<%= @form.select @field.id_input_foreign_key,
options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
<% else %>
<%= @form.select @field.id_input_foreign_key, @field.options,
{
value: @resource.model[@field.id_input_foreign_key].to_s,
include_blank: @field.placeholder,
value: @field.value
},
{
class: classes("w-full"),
data: @field.get_html(:data, view: view, element: :input),
disabled: disabled
disabled: disabled,
style: @field.get_html(:style, view: view, element: :input)
}
%>
<%
<%
# If the select field is disabled, no value will be sent. It's how HTML works.
# Thus the extra hidden field to actually send the related id to the server.
if disabled %>
<%= @form.hidden_field @field.id_input_foreign_key %>
<% end %>
<%= @form.hidden_field @field.id_input_foreign_key %>
<% end %>
<% end %>
<% if !disabled && create_path.present? %>
<%= link_to t("avo.create_new_item", item: @field.name.downcase), create_path, class: "text-sm" %>
<% end %>
</div>
<% end %>
</div>
<% else %>
<%= field_wrapper **field_wrapper_args do %>
<% if @field.searchable %>
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
field: @field,
model_key: @field.target_resource&.model_key,
foreign_key: @field.id_input_foreign_key,
resource: @resource,
disabled: disabled,
classes: classes("w-full"),
view: view,
style: @field.get_html(:style, view: view, element: :input)
%>
<% else %>
<%= @form.select @field.id_input_foreign_key, @field.options,
{
include_blank: @field.placeholder,
value: @field.value
},
{
class: classes("w-full"),
data: @field.get_html(:data, view: view, element: :input),
disabled: disabled,
style: @field.get_html(:style, view: view, element: :input)
}
%>
<%
# If the select field is disabled, no value will be sent. It's how HTML works.
# Thus the extra hidden field to actually send the related id to the server.
if disabled %>
<%= @form.hidden_field @field.id_input_foreign_key %>
<% end %>
<% end %>
<% end %>
<% end %>
</div>
11 changes: 11 additions & 0 deletions app/components/avo/fields/belongs_to_field/edit_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ def field_html_action
@field.get_html(:data, view: view, element: :input).fetch(:action, nil)
end

def create_path(target_resource = nil)
return nil if @resource.blank?

helpers.new_resource_path(**{
via_relation: @field.id.to_s,
resource: target_resource || @field.target_resource,
via_resource_id: resource.model.to_param,
via_belongs_to_resource_class: resource.class.name
}.compact)
end

private

def visit_through_association?
Expand Down
27 changes: 17 additions & 10 deletions app/components/avo/modal_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@
data-modal-target="modal"
>
<div aria-expanded="true" class="modal-overlay absolute w-full h-full bg-opacity-25 bg-gray-800 flex justify-center items-center" data-action="click->modal#close"></div>
<div aria-expanded="true" role="dialog" aria-modal="true" class="modal-body rounded-lg inset-auto w-11/12 lg:w-1/2 sm:max-w-168 min-h-1/4 bg-white flex z-50 relative shadow-modal overflow-auto max-h-full">
<div aria-expanded="true" role="dialog" aria-modal="true" class="modal-body rounded-lg inset-auto bg-white flex z-50 relative shadow-modal overflow-auto <%= width_classes %> <%= height_classes %>">
<div class="flex-1 flex flex-col justify-between">
<div>
<div class="p-6 text-2xl tracking-normal font-semibold text-black">
<%= heading %>
</div>
<div class="px-6 text-base text-gray-500">
<%= content %>
</div>
</div>
<div class="flex justify-end items-baseline space-x-4 p-4 bg-gray-100">
<%= controls %>
<% if heading? %>
<div class="p-6 text-2xl tracking-normal font-semibold text-black">
<%= heading %>
</div>
<% end %>

<% if content? %>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this content? call is causing a NoMethodError

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@segiddins can you describe the scenario? I wasn't able to reproduce it

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rubygems/rubygems.org#4154 yeah, tests started failing on the version bump

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might need to update view_component on your end.
I can't put my finger on it exactly, but at some point they changed the way they figure out if the slot has any content inside.

Copy link
Collaborator

@adrianthedev adrianthedev Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that view_component v3 added this helper. Are you able to update to VC 3 @segiddins?
I see that you are already on 2.8+ which should not bring breaking changes AFAIK.

<div class="px-6 text-base text-gray-500 <%= body_class %>">
<%= content %>
</div>
<% end %>
</div>
<% if controls? %>
<div class="flex justify-end items-baseline space-x-4 p-4 bg-gray-100">
<%= controls %>
</div>
<% end %>
</div>
</div>
</div>
Expand Down
21 changes: 21 additions & 0 deletions app/components/avo/modal_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,25 @@
class Avo::ModalComponent < ViewComponent::Base
renders_one :heading
renders_one :controls

attr_reader :width
attr_reader :body_class

def initialize(width: :md, body_class: nil)
@width = width
@body_class = body_class
end

def width_classes
case width.to_sym
when :md
"w-11/12 lg:w-1/2 sm:max-w-168"
when :xl
"w-11/12 lg:w-3/4"
end
end

def height_classes
"max-h-full min-h-1/4 max-h-11/12"
end
end
4 changes: 2 additions & 2 deletions app/components/avo/panel_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
</div>
</div>
<% if sidebar? %>
<div class="w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
<div class="max-w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
<%= sidebar %>
</div>
<% end %>
<% if bare_sidebar? %>
<div class="w-full sm:w-1/3 flex-shrink-0 h-full">
<div class="max-w-full sm:w-1/3 flex-shrink-0 h-full">
<%= bare_sidebar %>
</div>
<% end %>
Expand Down
1 change: 1 addition & 0 deletions app/components/avo/referrer_params_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
<%= hidden_field_tag :via_resource_class, params[:via_resource_class] if params[:via_resource_class] %>
<%= hidden_field_tag :via_resource_id, params[:via_resource_id] if params[:via_resource_id] %>
<%= hidden_field_tag :via_relation, params[:via_relation] if params[:via_relation] %>
<%= hidden_field_tag :via_belongs_to_resource_class, params[:via_belongs_to_resource_class] if params[:via_belongs_to_resource_class] %>
<%= hidden_field_tag :referrer, back_path if params[:via_resource_class] %>
17 changes: 11 additions & 6 deletions app/components/avo/views/resource_edit_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<%= content_tag :div,
id: helpers.frame_id(@resource),
data: {
model_name: @resource.model_name.to_s,
resource_name: @resource.class.to_s,
Expand All @@ -18,12 +19,16 @@
multipart: true do |form| %>
<%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
<%= content_tag :div, class: 'space-y-12' do %>
<%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
<%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description,
display_breadcrumbs: display_breadcrumbs?,
index: 0, data: { panel_id: "main" }) do |c| %>
<% c.with_tools do %>
<%= a_link back_path,
style: :text,
icon: 'arrow-left' do %>
<%= t('avo.cancel').capitalize %>
<% if back_path.present? %>
<%= a_link back_path,
style: :text,
icon: 'arrow-left' do %>
<%= t('avo.cancel').capitalize %>
<% end %>
<% end %>
<% if can_see_the_destroy_button? %>
<%= a_link destroy_path,
Expand Down Expand Up @@ -68,7 +73,7 @@
</div>
<% end %>
<% end %>
<% if sidebar.present? %>
<% if sidebar.present? && sidebar.visible_items.present? %>
<% c.with_sidebar do %>
<%= render sidebar_component form: form %>
<% end %>
Expand Down
Loading
Loading