avo 2.42.2 → 2.43.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of avo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +118 -96
- data/app/components/avo/fields/belongs_to_field/edit_component.rb +11 -0
- data/app/components/avo/fields/tags_field/edit_component.html.erb +1 -0
- data/app/components/avo/modal_component.html.erb +17 -10
- data/app/components/avo/modal_component.rb +21 -0
- data/app/components/avo/panel_component.html.erb +2 -2
- data/app/components/avo/referrer_params_component.html.erb +1 -0
- data/app/components/avo/views/resource_edit_component.html.erb +11 -6
- data/app/components/avo/views/resource_edit_component.rb +11 -1
- data/app/controllers/avo/actions_controller.rb +1 -1
- data/app/controllers/avo/base_controller.rb +11 -2
- data/app/helpers/avo/application_helper.rb +4 -0
- data/app/javascript/js/controllers/fields/reload_belongs_to_field_controller.js +51 -0
- data/app/javascript/js/controllers/fields/tags_field_controller.js +2 -1
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/actions/show.html.erb +2 -0
- data/app/views/avo/base/_new_via_belongs_to.html.erb +12 -0
- data/app/views/avo/base/close_modal_and_reload_field.turbo_stream.erb +8 -0
- data/app/views/avo/base/create_fail_action.turbo_stream.erb +13 -0
- data/app/views/avo/partials/_flash_alerts.turbo_stream.erb +3 -0
- data/lib/avo/fields/tags_field.rb +2 -0
- data/lib/avo/licensing/h_q.rb +4 -2
- data/lib/avo/version.rb +1 -1
- data/public/avo-assets/avo.base.css +20 -8
- data/public/avo-assets/avo.base.js +84 -84
- data/public/avo-assets/avo.base.js.map +3 -3
- metadata +7 -3
- data/app/views/avo/actions/keep_modal_open.turbo_stream.erb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e46a221f03c28ba77e3a9ab32d841176eb9ecf7125161742ad0e1c222063d1b0
|
4
|
+
data.tar.gz: 53a6aced2acfe8347472915e2d1c757ab7125b4189c333b1a849b23e51e306f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c911463f13e7556ac9a9340ab45d67e18ee9130a542d5d12db3d728dbf62e4abe74f09ffc6cf9bfb4fa5b578396461715ba47ffc2a006680a6460ec25e748bd
|
7
|
+
data.tar.gz: 7399602dd68b5442f19ed38a483373c95cde447a43fba2108e6e3e76905888f547841fd81675700c7467d0f035aab5a5563cc2a870ced29faf74581818266396
|
data/Gemfile.lock
CHANGED
@@ -1,115 +1,137 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
%>
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
<%= field_wrapper **field_wrapper_args, help: @field.polymorphic_help || '' do %>
|
16
|
-
<%= @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] },
|
17
|
-
{
|
18
|
-
value: @field.value,
|
19
|
-
include_blank: @field.placeholder,
|
20
|
-
},
|
21
|
-
{
|
22
|
-
class: classes("w-full"),
|
23
|
-
data: {
|
24
|
-
**@field.get_html(:data, view: view, element: :input),
|
25
|
-
action: "change->belongs-to-field#changeType #{field_html_action}",
|
26
|
-
'belongs-to-field-target': "select",
|
27
|
-
},
|
28
|
-
disabled: disabled
|
29
|
-
}
|
1
|
+
<div data-controller="reload-belongs-to-field"
|
2
|
+
data-action="turbo:before-stream-render@document->reload-belongs-to-field#beforeStreamRender"
|
3
|
+
data-reload-belongs-to-field-polymorphic-value="<%= is_polymorphic? %>"
|
4
|
+
data-reload-belongs-to-field-searchable-value="<%= @field.searchable %>"
|
5
|
+
data-reload-belongs-to-field-relation-name-value="<%= @field.id %>"
|
6
|
+
data-reload-belongs-to-field-target-name-value="<%= form.object_name %>[<%= @field.id_input_foreign_key %>]"
|
7
|
+
>
|
8
|
+
<% if is_polymorphic? %>
|
9
|
+
<%
|
10
|
+
# Set the model keys so we can pass them over
|
11
|
+
model_keys = @field.types.map do |type|
|
12
|
+
resource = Avo::App.get_resource_by_model_name(type.to_s)
|
13
|
+
[type.to_s, resource.model_key]
|
14
|
+
end.to_h
|
30
15
|
%>
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
16
|
+
<div class="divide-y"
|
17
|
+
data-controller="belongs-to-field"
|
18
|
+
data-searchable="<%= @field.searchable %>"
|
19
|
+
data-association="<%= @field.id %>"
|
20
|
+
data-association-class="<%= @field&.target_resource&.model_class || nil %>"
|
21
|
+
>
|
22
|
+
<%= field_wrapper **field_wrapper_args, help: @field.polymorphic_help || '' do %>
|
23
|
+
<%= @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] },
|
24
|
+
{
|
25
|
+
value: @field.value,
|
26
|
+
include_blank: @field.placeholder,
|
27
|
+
},
|
28
|
+
{
|
29
|
+
class: classes("w-full"),
|
30
|
+
data: {
|
31
|
+
**@field.get_html(:data, view: view, element: :input),
|
32
|
+
action: "change->belongs-to-field#changeType #{field_html_action}",
|
33
|
+
'belongs-to-field-target': "select",
|
34
|
+
},
|
35
|
+
disabled: disabled
|
36
|
+
}
|
37
|
+
%>
|
38
|
+
<%
|
39
|
+
# If the select field is disabled, no value will be sent. It's how HTML works.
|
40
|
+
# Thus the extra hidden field to actually send the related id to the server.
|
41
|
+
if disabled %>
|
42
|
+
<%= @form.hidden_field @field.type_input_foreign_key %>
|
43
|
+
<% end %>
|
36
44
|
<% end %>
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
45
|
+
<% @field.types.each do |type| %>
|
46
|
+
<div class="hidden"
|
47
|
+
data-belongs-to-field-target="type"
|
48
|
+
data-type="<%= type %>"
|
49
|
+
>
|
50
|
+
<%= field_wrapper **field_wrapper_args, label: ::Avo::App.get_resource_by_model_name(type.to_s).name do %>
|
51
|
+
<div class="flex flex-col gap-1">
|
52
|
+
<% if @field.searchable %>
|
53
|
+
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
|
54
|
+
disabled: disabled,
|
55
|
+
field: @field,
|
56
|
+
foreign_key: @field.id_input_foreign_key,
|
57
|
+
model_key: model_keys[type.to_s],
|
58
|
+
polymorphic_record: polymorphic_record,
|
59
|
+
resource: @resource,
|
60
|
+
style: @field.get_html(:style, view: view, element: :input),
|
61
|
+
type: type,
|
62
|
+
classes: classes("w-full"),
|
63
|
+
view: view
|
64
|
+
%>
|
65
|
+
<% else %>
|
66
|
+
<%= @form.select @field.id_input_foreign_key,
|
67
|
+
options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
|
68
|
+
{
|
69
|
+
value: @resource.model[@field.id_input_foreign_key].to_s,
|
70
|
+
include_blank: @field.placeholder,
|
71
|
+
},
|
72
|
+
{
|
73
|
+
class: classes("w-full"),
|
74
|
+
data: @field.get_html(:data, view: view, element: :input),
|
75
|
+
disabled: disabled
|
76
|
+
}
|
77
|
+
%>
|
78
|
+
<%
|
79
|
+
# If the select field is disabled, no value will be sent. It's how HTML works.
|
80
|
+
# Thus the extra hidden field to actually send the related id to the server.
|
81
|
+
if disabled %>
|
82
|
+
<%= @form.hidden_field @field.id_input_foreign_key %>
|
83
|
+
<% end %>
|
84
|
+
<% end %>
|
85
|
+
<% create_href = create_path(::Avo::App.get_resource_by_model_name(type.to_s)) %>
|
86
|
+
<% if !disabled && create_href.present? %>
|
87
|
+
<%= link_to t("avo.create_new_item", item: type.to_s.downcase),
|
88
|
+
create_href,
|
89
|
+
class: "text-sm"
|
90
|
+
%>
|
91
|
+
<% end %>
|
92
|
+
</div>
|
93
|
+
<% end %>
|
94
|
+
</div>
|
95
|
+
<% end %>
|
96
|
+
</div>
|
97
|
+
<% else %>
|
98
|
+
<%= field_wrapper **field_wrapper_args do %>
|
99
|
+
<div class="flex flex-col gap-1">
|
100
|
+
<% if @field.searchable %>
|
101
|
+
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
|
102
|
+
field: @field,
|
103
|
+
model_key: @field.target_resource&.model_key,
|
104
|
+
foreign_key: @field.id_input_foreign_key,
|
105
|
+
resource: @resource,
|
106
|
+
disabled: disabled,
|
107
|
+
classes: classes("w-full"),
|
108
|
+
view: view,
|
109
|
+
style: @field.get_html(:style, view: view, element: :input)
|
56
110
|
%>
|
57
|
-
|
58
|
-
|
59
|
-
options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
|
111
|
+
<% else %>
|
112
|
+
<%= @form.select @field.id_input_foreign_key, @field.options,
|
60
113
|
{
|
61
|
-
value: @resource.model[@field.id_input_foreign_key].to_s,
|
62
114
|
include_blank: @field.placeholder,
|
115
|
+
value: @field.value
|
63
116
|
},
|
64
117
|
{
|
65
118
|
class: classes("w-full"),
|
66
119
|
data: @field.get_html(:data, view: view, element: :input),
|
67
|
-
disabled: disabled
|
120
|
+
disabled: disabled,
|
121
|
+
style: @field.get_html(:style, view: view, element: :input)
|
68
122
|
}
|
69
123
|
%>
|
70
|
-
|
124
|
+
<%
|
71
125
|
# If the select field is disabled, no value will be sent. It's how HTML works.
|
72
126
|
# Thus the extra hidden field to actually send the related id to the server.
|
73
127
|
if disabled %>
|
74
|
-
|
75
|
-
<% end %>
|
128
|
+
<%= @form.hidden_field @field.id_input_foreign_key %>
|
76
129
|
<% end %>
|
77
130
|
<% end %>
|
131
|
+
<% if !disabled && create_path.present? %>
|
132
|
+
<%= link_to t("avo.create_new_item", item: @field.name.downcase), create_path, class: "text-sm" %>
|
133
|
+
<% end %>
|
78
134
|
</div>
|
79
135
|
<% end %>
|
80
|
-
</div>
|
81
|
-
<% else %>
|
82
|
-
<%= field_wrapper **field_wrapper_args do %>
|
83
|
-
<% if @field.searchable %>
|
84
|
-
<%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
|
85
|
-
field: @field,
|
86
|
-
model_key: @field.target_resource&.model_key,
|
87
|
-
foreign_key: @field.id_input_foreign_key,
|
88
|
-
resource: @resource,
|
89
|
-
disabled: disabled,
|
90
|
-
classes: classes("w-full"),
|
91
|
-
view: view,
|
92
|
-
style: @field.get_html(:style, view: view, element: :input)
|
93
|
-
%>
|
94
|
-
<% else %>
|
95
|
-
<%= @form.select @field.id_input_foreign_key, @field.options,
|
96
|
-
{
|
97
|
-
include_blank: @field.placeholder,
|
98
|
-
value: @field.value
|
99
|
-
},
|
100
|
-
{
|
101
|
-
class: classes("w-full"),
|
102
|
-
data: @field.get_html(:data, view: view, element: :input),
|
103
|
-
disabled: disabled,
|
104
|
-
style: @field.get_html(:style, view: view, element: :input)
|
105
|
-
}
|
106
|
-
%>
|
107
|
-
<%
|
108
|
-
# If the select field is disabled, no value will be sent. It's how HTML works.
|
109
|
-
# Thus the extra hidden field to actually send the related id to the server.
|
110
|
-
if disabled %>
|
111
|
-
<%= @form.hidden_field @field.id_input_foreign_key %>
|
112
|
-
<% end %>
|
113
|
-
<% end %>
|
114
136
|
<% end %>
|
115
|
-
|
137
|
+
</div>
|
@@ -57,6 +57,17 @@ class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
|
|
57
57
|
@field.get_html(:data, view: view, element: :input).fetch(:action, nil)
|
58
58
|
end
|
59
59
|
|
60
|
+
def create_path(target_resource = nil)
|
61
|
+
return nil if @resource.blank?
|
62
|
+
|
63
|
+
helpers.new_resource_path(**{
|
64
|
+
via_relation: @field.id.to_s,
|
65
|
+
resource: target_resource || @field.target_resource,
|
66
|
+
via_resource_id: resource.model.to_param,
|
67
|
+
via_belongs_to_resource_class: resource.class.name
|
68
|
+
}.compact)
|
69
|
+
end
|
70
|
+
|
60
71
|
private
|
61
72
|
|
62
73
|
def visit_through_association?
|
@@ -4,6 +4,7 @@
|
|
4
4
|
tags_field_whitelist_items_value: @field.suggestions.to_json,
|
5
5
|
tags_field_disallowed_items_value: @field.disallowed.to_json,
|
6
6
|
tags_field_enforce_suggestions_value: @field.enforce_suggestions,
|
7
|
+
tags_field_suggestions_max_items_value: @field.suggestions_max_items,
|
7
8
|
tags_field_close_on_select_value: @field.close_on_select,
|
8
9
|
tags_field_delimiters_value: @field.delimiters,
|
9
10
|
tags_field_fetch_values_from_value: @field.fetch_values_from,
|
@@ -3,19 +3,26 @@
|
|
3
3
|
data-modal-target="modal"
|
4
4
|
>
|
5
5
|
<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>
|
6
|
-
<div aria-expanded="true" role="dialog" aria-modal="true" class="modal-body rounded-lg inset-auto
|
6
|
+
<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 %>">
|
7
7
|
<div class="flex-1 flex flex-col justify-between">
|
8
8
|
<div>
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
<% if heading? %>
|
10
|
+
<div class="p-6 text-2xl tracking-normal font-semibold text-black">
|
11
|
+
<%= heading %>
|
12
|
+
</div>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<% if content? %>
|
16
|
+
<div class="px-6 text-base text-gray-500 <%= body_class %>">
|
17
|
+
<%= content %>
|
18
|
+
</div>
|
19
|
+
<% end %>
|
18
20
|
</div>
|
21
|
+
<% if controls? %>
|
22
|
+
<div class="flex justify-end items-baseline space-x-4 p-4 bg-gray-100">
|
23
|
+
<%= controls %>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
19
26
|
</div>
|
20
27
|
</div>
|
21
28
|
</div>
|
@@ -3,4 +3,25 @@
|
|
3
3
|
class Avo::ModalComponent < ViewComponent::Base
|
4
4
|
renders_one :heading
|
5
5
|
renders_one :controls
|
6
|
+
|
7
|
+
attr_reader :width
|
8
|
+
attr_reader :body_class
|
9
|
+
|
10
|
+
def initialize(width: :md, body_class: nil)
|
11
|
+
@width = width
|
12
|
+
@body_class = body_class
|
13
|
+
end
|
14
|
+
|
15
|
+
def width_classes
|
16
|
+
case width.to_sym
|
17
|
+
when :md
|
18
|
+
"w-11/12 lg:w-1/2 sm:max-w-168"
|
19
|
+
when :xl
|
20
|
+
"w-11/12 lg:w-3/4"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def height_classes
|
25
|
+
"max-h-full min-h-1/4 max-h-11/12"
|
26
|
+
end
|
6
27
|
end
|
@@ -32,12 +32,12 @@
|
|
32
32
|
</div>
|
33
33
|
</div>
|
34
34
|
<% if sidebar? %>
|
35
|
-
<div class="w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
|
35
|
+
<div class="max-w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
|
36
36
|
<%= sidebar %>
|
37
37
|
</div>
|
38
38
|
<% end %>
|
39
39
|
<% if bare_sidebar? %>
|
40
|
-
<div class="w-full sm:w-1/3 flex-shrink-0 h-full">
|
40
|
+
<div class="max-w-full sm:w-1/3 flex-shrink-0 h-full">
|
41
41
|
<%= bare_sidebar %>
|
42
42
|
</div>
|
43
43
|
<% end %>
|
@@ -3,4 +3,5 @@
|
|
3
3
|
<%= hidden_field_tag :via_resource_class, params[:via_resource_class] if params[:via_resource_class] %>
|
4
4
|
<%= hidden_field_tag :via_resource_id, params[:via_resource_id] if params[:via_resource_id] %>
|
5
5
|
<%= hidden_field_tag :via_relation, params[:via_relation] if params[:via_relation] %>
|
6
|
+
<%= hidden_field_tag :via_belongs_to_resource_class, params[:via_belongs_to_resource_class] if params[:via_belongs_to_resource_class] %>
|
6
7
|
<%= hidden_field_tag :referrer, back_path if params[:via_resource_class] %>
|
@@ -1,4 +1,5 @@
|
|
1
1
|
<%= content_tag :div,
|
2
|
+
id: helpers.frame_id(@resource),
|
2
3
|
data: {
|
3
4
|
model_name: @resource.model_name.to_s,
|
4
5
|
resource_name: @resource.class.to_s,
|
@@ -18,12 +19,16 @@
|
|
18
19
|
multipart: true do |form| %>
|
19
20
|
<%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
|
20
21
|
<%= content_tag :div, class: 'space-y-12' do %>
|
21
|
-
<%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description,
|
22
|
+
<%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description,
|
23
|
+
display_breadcrumbs: display_breadcrumbs?,
|
24
|
+
index: 0, data: { panel_id: "main" }) do |c| %>
|
22
25
|
<% c.with_tools do %>
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
<% if back_path.present? %>
|
27
|
+
<%= a_link back_path,
|
28
|
+
style: :text,
|
29
|
+
icon: 'arrow-left' do %>
|
30
|
+
<%= t('avo.cancel').capitalize %>
|
31
|
+
<% end %>
|
27
32
|
<% end %>
|
28
33
|
<% if can_see_the_destroy_button? %>
|
29
34
|
<%= a_link destroy_path,
|
@@ -68,7 +73,7 @@
|
|
68
73
|
</div>
|
69
74
|
<% end %>
|
70
75
|
<% end %>
|
71
|
-
<% if sidebar.present? %>
|
76
|
+
<% if sidebar.present? && sidebar.visible_items.present? %>
|
72
77
|
<% c.with_sidebar do %>
|
73
78
|
<%= render sidebar_component form: form %>
|
74
79
|
<% end %>
|
@@ -4,11 +4,12 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
|
|
4
4
|
include Avo::ResourcesHelper
|
5
5
|
include Avo::ApplicationHelper
|
6
6
|
|
7
|
-
def initialize(resource: nil, model: nil, actions: [], view: :edit)
|
7
|
+
def initialize(resource: nil, model: nil, actions: [], view: :edit, display_breadcrumbs: true)
|
8
8
|
@resource = resource
|
9
9
|
@model = model
|
10
10
|
@actions = actions
|
11
11
|
@view = view
|
12
|
+
@display_breadcrumbs = display_breadcrumbs
|
12
13
|
end
|
13
14
|
|
14
15
|
def title
|
@@ -16,6 +17,7 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def back_path
|
20
|
+
return if via_belongs_to?
|
19
21
|
return resource_view_path if via_resource?
|
20
22
|
return resources_path if via_index?
|
21
23
|
|
@@ -46,12 +48,20 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
|
|
46
48
|
@resource.authorization.authorize_action @view, raise_exception: false
|
47
49
|
end
|
48
50
|
|
51
|
+
def display_breadcrumbs?
|
52
|
+
@reflection.blank? && @display_breadcrumbs
|
53
|
+
end
|
54
|
+
|
49
55
|
private
|
50
56
|
|
51
57
|
def via_index?
|
52
58
|
params[:via_view] == "index"
|
53
59
|
end
|
54
60
|
|
61
|
+
def via_belongs_to?
|
62
|
+
params[:via_belongs_to_resource_class].present?
|
63
|
+
end
|
64
|
+
|
55
65
|
def is_edit?
|
56
66
|
view.in?([:edit, :update])
|
57
67
|
end
|
@@ -107,6 +107,11 @@ module Avo
|
|
107
107
|
@model = @resource.model_class.new
|
108
108
|
@resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
|
109
109
|
|
110
|
+
# Handle special cases when creating a new record via a belongs_to relationship
|
111
|
+
if params[:via_belongs_to_resource_class].present?
|
112
|
+
return render turbo_stream: turbo_stream.append('attach_modal', partial: 'avo/base/new_via_belongs_to')
|
113
|
+
end
|
114
|
+
|
110
115
|
set_actions
|
111
116
|
|
112
117
|
@page_title = @resource.default_panel_name.to_s
|
@@ -130,7 +135,7 @@ module Avo
|
|
130
135
|
@resource.hydrate(model: @model, view: :new, user: _current_user)
|
131
136
|
|
132
137
|
# This means that the record has been created through another parent record and we need to attach it somehow.
|
133
|
-
if params[:via_resource_id].present?
|
138
|
+
if params[:via_resource_id].present? && params[:via_belongs_to_resource_class].nil?
|
134
139
|
@reflection = @model._reflections[params[:via_relation]]
|
135
140
|
# Figure out what kind of association does the record have with the parent record
|
136
141
|
|
@@ -420,15 +425,19 @@ module Avo
|
|
420
425
|
end
|
421
426
|
|
422
427
|
def create_success_action
|
428
|
+
return render "close_modal_and_reload_field" if params[:via_belongs_to_resource_class].present?
|
429
|
+
|
423
430
|
respond_to do |format|
|
424
431
|
format.html { redirect_to after_create_path, notice: create_success_message}
|
425
432
|
end
|
426
433
|
end
|
427
434
|
|
428
435
|
def create_fail_action
|
436
|
+
flash.now[:error] = create_fail_message
|
437
|
+
|
429
438
|
respond_to do |format|
|
430
|
-
flash.now[:error] = create_fail_message
|
431
439
|
format.html { render :new, status: :unprocessable_entity }
|
440
|
+
format.turbo_stream { render "create_fail_action" }
|
432
441
|
end
|
433
442
|
end
|
434
443
|
|
@@ -119,6 +119,10 @@ module Avo
|
|
119
119
|
Avo::Filters::BaseFilter.encode_filters(filter_params)
|
120
120
|
end
|
121
121
|
|
122
|
+
def frame_id(resource)
|
123
|
+
["frame", resource.model_name.singular, resource.model.id].compact.join("-")
|
124
|
+
end
|
125
|
+
|
122
126
|
private
|
123
127
|
|
124
128
|
# Taken from the original library
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
// Used as a custom Stream Action <turbo-stream action="update-belongs-to" />
|
4
|
+
export default class extends Controller {
|
5
|
+
static values = {
|
6
|
+
polymorphic: Boolean,
|
7
|
+
searchable: Boolean,
|
8
|
+
targetName: String,
|
9
|
+
relationName: String
|
10
|
+
}
|
11
|
+
|
12
|
+
beforeStreamRender(event) {
|
13
|
+
const { relationName } = event.target.dataset
|
14
|
+
if (event.target.action !== "update-belongs-to" || this.relationNameValue !== relationName) {
|
15
|
+
return false;
|
16
|
+
}
|
17
|
+
|
18
|
+
event.detail.render = (stream) => {
|
19
|
+
if (this.searchableValue) {
|
20
|
+
this.updateSearchable(stream)
|
21
|
+
} else{
|
22
|
+
this.updateNonSearchable(stream)
|
23
|
+
}
|
24
|
+
};
|
25
|
+
}
|
26
|
+
|
27
|
+
updateSearchable(stream) {
|
28
|
+
// Update the id component
|
29
|
+
document.querySelector(`input[name="${this.targetNameValue}"][type="hidden"]`).value = stream.dataset.targetResourceId
|
30
|
+
// Update the label
|
31
|
+
document.querySelector(`input[name="${this.targetNameValue}"][type="text"]`).value = stream.dataset.targetResourceLabel
|
32
|
+
}
|
33
|
+
|
34
|
+
updateNonSearchable(stream) {
|
35
|
+
const select = this.selectorContext(stream).querySelector(`select[name="${this.targetNameValue}"]`)
|
36
|
+
const option = document.createElement('option')
|
37
|
+
option.value = stream.dataset.targetResourceId
|
38
|
+
option.text = stream.dataset.targetResourceLabel
|
39
|
+
option.selected = true
|
40
|
+
select.appendChild(option)
|
41
|
+
}
|
42
|
+
|
43
|
+
selectorContext(stream) {
|
44
|
+
// if polymorphic, search for the select in the correct sub-container
|
45
|
+
if (this.polymorphicValue) {
|
46
|
+
return document.querySelector(`[data-type="${stream.dataset.targetResourceClass}"]`)
|
47
|
+
}
|
48
|
+
|
49
|
+
return document
|
50
|
+
}
|
51
|
+
}
|
@@ -15,6 +15,7 @@ export default class extends BaseController {
|
|
15
15
|
enforceSuggestions: { type: Boolean, default: false },
|
16
16
|
closeOnSelect: { type: Boolean, default: false },
|
17
17
|
delimiters: { type: Array, default: [] },
|
18
|
+
suggestionsMaxItems: { type: Number, default: 20 },
|
18
19
|
mode: String,
|
19
20
|
fetchValuesFrom: String,
|
20
21
|
}
|
@@ -36,7 +37,7 @@ export default class extends BaseController {
|
|
36
37
|
enforceWhitelist: this.enforceSuggestionsValue || this.fetchValuesFromValue,
|
37
38
|
delimiters: this.delimitersValue.join('|'),
|
38
39
|
dropdown: {
|
39
|
-
maxItems:
|
40
|
+
maxItems: this.suggestionsMaxItemsValue,
|
40
41
|
enabled: 0,
|
41
42
|
searchKeys: [this.labelAttributeValue],
|
42
43
|
closeOnSelect: this.closeOnSelectValue,
|
@@ -21,6 +21,7 @@ import ModalController from './controllers/modal_controller'
|
|
21
21
|
import MultipleSelectFilterController from './controllers/multiple_select_filter_controller'
|
22
22
|
import PerPageController from './controllers/per_page_controller'
|
23
23
|
import ProgressBarFieldController from './controllers/fields/progress_bar_field_controller'
|
24
|
+
import ReloadBelongsToFieldController from './controllers/fields/reload_belongs_to_field_controller'
|
24
25
|
import ResourceEditController from './controllers/resource_edit_controller'
|
25
26
|
import ResourceIndexController from './controllers/resource_index_controller'
|
26
27
|
import ResourceShowController from './controllers/resource_show_controller'
|
@@ -70,6 +71,7 @@ application.register('date-field', DateFieldController)
|
|
70
71
|
application.register('easy-mde', EasyMdeController)
|
71
72
|
application.register('key-value', KeyValueController)
|
72
73
|
application.register('progress-bar-field', ProgressBarFieldController)
|
74
|
+
application.register("reload-belongs-to-field", ReloadBelongsToFieldController)
|
73
75
|
application.register('trix-field', TrixFieldController)
|
74
76
|
|
75
77
|
// Custom controllers
|
@@ -17,6 +17,7 @@
|
|
17
17
|
<% c.with_heading do %>
|
18
18
|
<%= @action.action_name %>
|
19
19
|
<% end %>
|
20
|
+
|
20
21
|
<div class="flex-1 flex">
|
21
22
|
<%= @action.get_message %>
|
22
23
|
</div>
|
@@ -34,6 +35,7 @@
|
|
34
35
|
<% end %>
|
35
36
|
</div>
|
36
37
|
<% end %>
|
38
|
+
|
37
39
|
<% c.with_controls do %>
|
38
40
|
<%= a_button type: :button,
|
39
41
|
data: { action: 'click->modal#close' },
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%= turbo_frame_tag "new_via_belongs_to" do %>
|
2
|
+
<%= render(Avo::ModalComponent.new(width: :xl, body_class: "bg-application")) do |c| %>
|
3
|
+
<div class="pt-4 pb-8">
|
4
|
+
<%= render Avo::Views::ResourceEditComponent.new(
|
5
|
+
resource: @resource,
|
6
|
+
model: @model,
|
7
|
+
view: @view,
|
8
|
+
display_breadcrumbs: false
|
9
|
+
) %>
|
10
|
+
</div>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<%= turbo_stream.remove("new_via_belongs_to") %>
|
2
|
+
|
3
|
+
<turbo-stream action="update-belongs-to"
|
4
|
+
data-relation-name="<%= params[:via_relation] %>"
|
5
|
+
data-target-resource-id="<%= @model.id %>"
|
6
|
+
data-target-resource-label="<%= @resource.label %>"
|
7
|
+
data-target-resource-class="<%= @model.class.name %>">
|
8
|
+
</turbo-stream>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<%= turbo_stream.replace(frame_id(@resource), template: "avo/base/new") %>
|
2
|
+
|
3
|
+
<%= turbo_stream.append "alerts" do %>
|
4
|
+
<%= render Avo::FlashAlertsComponent.new flashes: flash %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<% if @model.errors.any? %>
|
8
|
+
<%= turbo_stream.append("alerts") do %>
|
9
|
+
<% @model.errors.full_messages.each do |message| %>
|
10
|
+
<%= render Avo::AlertComponent.new :error, message %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|