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.

Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +118 -96
  4. data/app/components/avo/fields/belongs_to_field/edit_component.rb +11 -0
  5. data/app/components/avo/fields/tags_field/edit_component.html.erb +1 -0
  6. data/app/components/avo/modal_component.html.erb +17 -10
  7. data/app/components/avo/modal_component.rb +21 -0
  8. data/app/components/avo/panel_component.html.erb +2 -2
  9. data/app/components/avo/referrer_params_component.html.erb +1 -0
  10. data/app/components/avo/views/resource_edit_component.html.erb +11 -6
  11. data/app/components/avo/views/resource_edit_component.rb +11 -1
  12. data/app/controllers/avo/actions_controller.rb +1 -1
  13. data/app/controllers/avo/base_controller.rb +11 -2
  14. data/app/helpers/avo/application_helper.rb +4 -0
  15. data/app/javascript/js/controllers/fields/reload_belongs_to_field_controller.js +51 -0
  16. data/app/javascript/js/controllers/fields/tags_field_controller.js +2 -1
  17. data/app/javascript/js/controllers.js +2 -0
  18. data/app/views/avo/actions/show.html.erb +2 -0
  19. data/app/views/avo/base/_new_via_belongs_to.html.erb +12 -0
  20. data/app/views/avo/base/close_modal_and_reload_field.turbo_stream.erb +8 -0
  21. data/app/views/avo/base/create_fail_action.turbo_stream.erb +13 -0
  22. data/app/views/avo/partials/_flash_alerts.turbo_stream.erb +3 -0
  23. data/lib/avo/fields/tags_field.rb +2 -0
  24. data/lib/avo/licensing/h_q.rb +4 -2
  25. data/lib/avo/version.rb +1 -1
  26. data/public/avo-assets/avo.base.css +20 -8
  27. data/public/avo-assets/avo.base.js +84 -84
  28. data/public/avo-assets/avo.base.js.map +3 -3
  29. metadata +7 -3
  30. 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: 85975e5896ef4f49e8ca3b4874470092dfd64eafd7c0282a8715162c9dfc42bc
4
- data.tar.gz: c90db50a988c7cc7bf66cbc5671844390f28455f4d09f421b86d49caf86f588a
3
+ metadata.gz: e46a221f03c28ba77e3a9ab32d841176eb9ecf7125161742ad0e1c222063d1b0
4
+ data.tar.gz: 53a6aced2acfe8347472915e2d1c757ab7125b4189c333b1a849b23e51e306f5
5
5
  SHA512:
6
- metadata.gz: 98b4bc5ccb0193c5ef46da23c582a3da7ee3bf8fc8ae5516232b4ea6f0b98df0fa3a798be6498019e76aa735fd37b6cbfff1d6514d3be3a6628b6b7af56e6431
7
- data.tar.gz: ef6fee4b84d1cbf589a0160785005e568c2c8f2a6dad77da7692170c8819e89ad7202ee4f26944180c34a4bd19e6953c50ad96534ce8ca33e2582745ec7bf3c6
6
+ metadata.gz: 6c911463f13e7556ac9a9340ab45d67e18ee9130a542d5d12db3d728dbf62e4abe74f09ffc6cf9bfb4fa5b578396461715ba47ffc2a006680a6460ec25e748bd
7
+ data.tar.gz: 7399602dd68b5442f19ed38a483373c95cde447a43fba2108e6e3e76905888f547841fd81675700c7467d0f035aab5a5563cc2a870ced29faf74581818266396
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.42.2)
4
+ avo (2.43.0)
5
5
  actionview (>= 6.0)
6
6
  active_link_to
7
7
  activerecord (>= 6.0)
@@ -1,115 +1,137 @@
1
- <% if is_polymorphic? %>
2
- <%
3
- # Set the model keys so we can pass them over
4
- model_keys = @field.types.map do |type|
5
- resource = Avo::App.get_resource_by_model_name(type.to_s)
6
- [type.to_s, resource.model_key]
7
- end.to_h
8
- %>
9
- <div class="divide-y"
10
- data-controller="belongs-to-field"
11
- data-searchable="<%= @field.searchable %>"
12
- data-association="<%= @field.id %>"
13
- data-association-class="<%= @field&.target_resource&.model_class || nil %>"
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
- # If the select field is disabled, no value will be sent. It's how HTML works.
33
- # Thus the extra hidden field to actually send the related id to the server.
34
- if disabled %>
35
- <%= @form.hidden_field @field.type_input_foreign_key %>
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
- <% end %>
38
- <% @field.types.each do |type| %>
39
- <div class="hidden"
40
- data-belongs-to-field-target="type"
41
- data-type="<%= type %>"
42
- >
43
- <%= field_wrapper **field_wrapper_args, label: ::Avo::App.get_resource_by_model_name(type.to_s).name do %>
44
- <% if @field.searchable %>
45
- <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
46
- disabled: disabled,
47
- field: @field,
48
- foreign_key: @field.id_input_foreign_key,
49
- model_key: model_keys[type.to_s],
50
- polymorphic_record: polymorphic_record,
51
- resource: @resource,
52
- style: @field.get_html(:style, view: view, element: :input),
53
- type: type,
54
- classes: classes("w-full"),
55
- view: view
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
- <% else %>
58
- <%= @form.select @field.id_input_foreign_key,
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
- <%= @form.hidden_field @field.id_input_foreign_key %>
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
- <% end %>
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 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">
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
- <div class="p-6 text-2xl tracking-normal font-semibold text-black">
10
- <%= heading %>
11
- </div>
12
- <div class="px-6 text-base text-gray-500">
13
- <%= content %>
14
- </div>
15
- </div>
16
- <div class="flex justify-end items-baseline space-x-4 p-4 bg-gray-100">
17
- <%= controls %>
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, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
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
- <%= a_link back_path,
24
- style: :text,
25
- icon: 'arrow-left' do %>
26
- <%= t('avo.cancel').capitalize %>
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
@@ -132,7 +132,7 @@ module Avo
132
132
 
133
133
  respond_to do |format|
134
134
  format.turbo_stream do
135
- render "keep_modal_open"
135
+ render partial: "avo/partials/flash_alerts"
136
136
  end
137
137
  end
138
138
  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: 20,
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 %>