avo 1.20.1 → 1.21.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +5 -3
  4. data/app/assets/svgs/x.svg +3 -0
  5. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +22 -0
  6. data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +33 -0
  7. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +39 -33
  8. data/app/components/avo/fields/common/files_list_viewer_component.html.erb +1 -1
  9. data/app/components/avo/fields/common/multiple_file_viewer_component.html.erb +2 -0
  10. data/app/components/avo/fields/common/multiple_file_viewer_component.rb +2 -1
  11. data/app/components/avo/fields/common/single_file_viewer_component.html.erb +2 -0
  12. data/app/components/avo/fields/common/single_file_viewer_component.rb +2 -1
  13. data/app/components/avo/fields/file_field/edit_component.html.erb +1 -1
  14. data/app/components/avo/fields/file_field/index_component.html.erb +3 -1
  15. data/app/components/avo/fields/file_field/show_component.html.erb +1 -1
  16. data/app/components/avo/resource_component.rb +1 -0
  17. data/app/components/avo/views/resource_index_component.html.erb +1 -1
  18. data/app/controllers/avo/relations_controller.rb +4 -4
  19. data/app/controllers/avo/search_controller.rb +0 -1
  20. data/app/javascript/js/controllers/fields/belongs_to_field_controller.js +96 -33
  21. data/app/javascript/js/controllers/search_controller.js +83 -17
  22. data/app/views/avo/partials/_global_search.html.erb +0 -1
  23. data/app/views/avo/partials/_javascript.html.erb +1 -0
  24. data/app/views/avo/partials/_resource_search.html.erb +0 -1
  25. data/db/factories.rb +4 -0
  26. data/lib/avo/fields/base_field.rb +1 -1
  27. data/lib/avo/fields/belongs_to_field.rb +19 -2
  28. data/lib/avo/fields/file_field.rb +2 -0
  29. data/lib/avo/fields/files_field.rb +2 -0
  30. data/lib/avo/licensing/pro_license.rb +2 -1
  31. data/lib/avo/version.rb +1 -1
  32. data/lib/generators/avo/templates/locales/avo.en.yml +1 -0
  33. data/public/avo-assets/avo.css +16 -0
  34. data/public/avo-assets/avo.js +317 -234
  35. data/public/avo-assets/avo.js.map +2 -2
  36. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e21fad20822df8f410cc1a6eca03d840949a4acd6bee9e47ea320927fa9c253d
4
- data.tar.gz: 61dc6595933b686526948f1ec9f7b37dfaad833f85928609d6bc296f9d6eb694
3
+ metadata.gz: ad998ae9691ee49081421f3db40c867fadf49c16d692722723277b43a631a1f7
4
+ data.tar.gz: 572ca7ed862e4e8bc3a784ed6c47402b5725ca9e99e286104f70739263e89590
5
5
  SHA512:
6
- metadata.gz: 04c07e302dc1cd1ab911edeed99543ab93dd7b8f29ddf460dd42a85e828412b07f3a5f9fd113eeb520185f712b5586f2be89b2cc58271fe3762b26acc7cb3c73
7
- data.tar.gz: '09968da7c74ed31cd98d3fe99ef64578bc26466b30db7d10a087ecc63424e42af2809f20c68df228637c4c54acd80e4144d35e64cb21df5d96833e14894c34f2'
6
+ metadata.gz: 3d69b2cb4c5691376fb8b3e7fcb26fabadde724d1de17fd015d03b94ddb4f833423bd018f033db10b02ad0c773d9151199d9c63acccdc59b707c9f8b18d42d8c
7
+ data.tar.gz: 40d1d605818269b2af621b3c4640a35117e4f53463151c0e339860ca763d8d8a04a7df4a56c1fa4f43ad8e8fa039ef61518055015bcc4d194cfaddcf430f048e
data/Gemfile CHANGED
@@ -30,7 +30,7 @@ gem "rails", "~> 6.1.0"
30
30
  # Use postgresql as the database for Active Record
31
31
  gem "pg", ">= 0.18", "< 2.0"
32
32
  # Use Puma as the app server
33
- gem "puma", "~> 5.5.1"
33
+ gem "puma", "~> 5.6.2"
34
34
  # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
35
35
  # gem "jbuilder", "~> 2.7"
36
36
  # Use Redis adapter to run Action Cable in production
@@ -77,6 +77,8 @@ group :development do
77
77
  # gem 'ruby-prof'
78
78
 
79
79
  # gem 'pry-rails'
80
+
81
+ gem 'htmlbeautifier'
80
82
  end
81
83
 
82
84
  group :development, :test do
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (1.20.1)
4
+ avo (1.21.0)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -175,6 +175,7 @@ GEM
175
175
  rails (>= 6.0.0)
176
176
  stimulus-rails
177
177
  turbo-rails
178
+ htmlbeautifier (1.4.1)
178
179
  httparty (0.20.0)
179
180
  mime-types (~> 3.0)
180
181
  multi_xml (>= 0.5.2)
@@ -237,7 +238,7 @@ GEM
237
238
  ast (~> 2.4.1)
238
239
  pg (1.3.1)
239
240
  public_suffix (4.0.6)
240
- puma (5.5.2)
241
+ puma (5.6.2)
241
242
  nio4r (~> 2.0)
242
243
  pundit (2.1.1)
243
244
  activesupport (>= 3.0.0)
@@ -417,6 +418,7 @@ DEPENDENCIES
417
418
  fuubar
418
419
  gem-release
419
420
  hotwire-rails
421
+ htmlbeautifier
420
422
  httparty
421
423
  image_processing (~> 1.2)
422
424
  iso
@@ -427,7 +429,7 @@ DEPENDENCIES
427
429
  meta-tags
428
430
  net-smtp
429
431
  pg (>= 0.18, < 2.0)
430
- puma (~> 5.5.1)
432
+ puma (~> 5.6.2)
431
433
  pundit
432
434
  rails (~> 6.1.0)
433
435
  rails-controller-testing
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
2
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
3
+ </svg>
@@ -0,0 +1,22 @@
1
+ <div data-controller="search" class="resource-search flex items-center h-full w-full" data-turbo-remove-before-cache>
2
+ <div class="w-full hidden"
3
+ data-search-target="autocomplete"
4
+ data-search-resource="<%= @model_key %>"
5
+ data-translation-keys='{"no_item_found": "<%= I18n.translate 'avo.no_item_found' %>"}'
6
+ data-via-association="belongs_to"
7
+ ></div>
8
+ <div class="relative w-full" autocomplete="off">
9
+ <%= @form.text_field @field.foreign_key,
10
+ value: field_label,
11
+ class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)), 'data-search-target': 'button clearValue',
12
+ placeholder: @field.placeholder,
13
+ disabled: true %>
14
+ <div class="absolute top-1/2 left-auto right-3 mr-px -mt-2 cursor-pointer hidden text-gray-500"
15
+ data-tippy="tooltip"
16
+ data-search-target="clearButton"
17
+ title="<%= I18n.translate 'avo.clear_value' %>"
18
+ data-action="click->search#clearValue"
19
+ ><%= helpers.svg 'x', class: 'h-4' %></div>
20
+ </div>
21
+ <%= @form.hidden_field @foreign_key, value: field_value, 'data-search-target': 'hiddenId clearValue' %>
22
+ </div>
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Fields::BelongsToField::AutocompleteComponent < ViewComponent::Base
4
+ def initialize(form:, field:, type: nil, model_key:, foreign_key:)
5
+ @form = form
6
+ @field = field
7
+ @type = type
8
+ @model_key = model_key
9
+ @foreign_key = foreign_key
10
+ end
11
+
12
+ def field_label
13
+ if searchable?
14
+ @field.value&.class == @type ? @field.field_label : nil
15
+ else
16
+ @field.field_label
17
+ end
18
+ end
19
+
20
+ def field_value
21
+ if searchable?
22
+ @field.value&.class == @type ? @field.field_value : nil
23
+ else
24
+ @field.field_value
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def searchable?
31
+ @type.present?
32
+ end
33
+ end
@@ -1,22 +1,34 @@
1
- <% if @field.types.present? %>
2
- <div data-controller="belongs-to-field">
1
+ <%
2
+ if @field.types.present? # It's a polymorphic association
3
+
4
+ # Set the model keys so we can pass them over
5
+ model_keys = @field.types.map do |type|
6
+ resource = Avo::App.get_resource_by_model_name(type.to_s)
7
+ [type.to_s, resource.model_key]
8
+ end.to_h
9
+ %>
10
+ <div 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
+ >
3
15
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
4
16
  <%= @form.select "#{@field.foreign_key}_type", @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
5
17
  {
18
+ value: @field.value,
6
19
  include_blank: @field.placeholder,
7
20
  },
8
21
  {
9
22
  class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
10
23
  disabled: disabled,
11
24
  'data-belongs-to-field-target': "select",
12
- 'data-action': 'change->belongs-to-field#changedType'
25
+ 'data-action': 'change->belongs-to-field#changeType'
13
26
  }
14
27
  %>
15
28
  <%
16
29
  # If the select field is disabled, no value will be sent. It's how HTML works.
17
30
  # Thus the extra hidden field to actually send the related id to the server.
18
- if disabled
19
- %>
31
+ if disabled %>
20
32
  <%= @form.hidden_field "#{@field.foreign_key}_type" %>
21
33
  <% end %>
22
34
  <% end %>
@@ -26,21 +38,18 @@
26
38
  data-type="<%= type %>"
27
39
  >
28
40
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, label: type.to_s.underscore.humanize do %>
29
- <%= @form.select "#{@field.foreign_key}_id", @field.values_for_type(type),
30
- {
31
- include_blank: @field.placeholder,
32
- },
33
- {
34
- class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
35
- disabled: disabled
36
- }
41
+ <% if @field.searchable %>
42
+ <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form, field: @field, type: type, model_key: model_keys[type.to_s], foreign_key: "#{@field.foreign_key}_id" %>
43
+ <% else %>
44
+ <%= @form.select "#{@field.foreign_key}_id", options_for_select(@field.values_for_type(type), @field.value&.class == type ? @field.field_value : nil),
45
+ {
46
+ include_blank: @field.placeholder,
47
+ },
48
+ {
49
+ class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
50
+ disabled: disabled
51
+ }
37
52
  %>
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
- %>
43
- <%= @form.hidden_field "#{@field.foreign_key}_id" %>
44
53
  <% end %>
45
54
  <% end %>
46
55
  </div>
@@ -48,21 +57,18 @@
48
57
  </div>
49
58
  <% else %>
50
59
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
51
- <%= @form.select @field.foreign_key, @field.options.map { |o| [o[:label], o[:value]] },
52
- {
53
- include_blank: @field.placeholder,
54
- },
55
- {
56
- class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
57
- disabled: disabled
58
- }
60
+ <% if @field.searchable %>
61
+ <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form, field: @field, model_key: @field.target_resource&.model_key, foreign_key: @field.foreign_key %>
62
+ <% else %>
63
+ <%= @form.select @field.foreign_key, @field.options.map { |o| [o[:label], o[:value]] },
64
+ {
65
+ include_blank: @field.placeholder,
66
+ },
67
+ {
68
+ class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
69
+ disabled: disabled
70
+ }
59
71
  %>
60
- <%
61
- # If the select field is disabled, no value will be sent. It's how HTML works.
62
- # Thus the extra hidden field to actually send the related id to the server.
63
- if disabled
64
- %>
65
- <%= @form.hidden_field @field.foreign_key %>
66
72
  <% end %>
67
73
  <% end %>
68
74
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <div class="relative p-3 bg-slate-200 grid grid-cols-3 xl:grid-cols-4 gap-3 rounded-xl">
2
2
  <% @field.value.attachments.each do |file| %>
3
- <%= render Avo::Fields::Common::MultipleFileViewerComponent.new id: @field.id, file: file, is_image: @field.is_image, button_size: :xs, resource: @resource %>
3
+ <%= render Avo::Fields::Common::MultipleFileViewerComponent.new id: @field.id, file: file, is_image: @field.is_image, is_audio: @field.is_audio, button_size: :xs, resource: @resource %>
4
4
  <% end %>
5
5
  </div>
@@ -2,6 +2,8 @@
2
2
  <% if @file.present? %>
3
3
  <% if @file.representable? && @is_image %>
4
4
  <%= image_tag helpers.main_app.url_for(@file), class: 'rounded-lg max-h-168 max-w-full' %>
5
+ <% elsif @is_audio %>
6
+ <%= audio_tag(helpers.main_app.url_for(@file), controls: true, preload: false, class: 'w-full')%>
5
7
  <% else %>
6
8
  <div class="relative flex flex-col justify-evenly items-center px-2 rounded-lg border bg-white border-gray-500 py-6 flex-1">
7
9
  <div class="flex flex-col justify-center items-center w-full">
@@ -3,10 +3,11 @@
3
3
  class Avo::Fields::Common::MultipleFileViewerComponent < ViewComponent::Base
4
4
  include Avo::ApplicationHelper
5
5
 
6
- def initialize(id:, file:, is_image:, direct_upload: false, resource:, button_size: :md)
6
+ def initialize(id:, file:, is_image:, is_audio:, direct_upload: false, resource:, button_size: :md)
7
7
  @id = id
8
8
  @file = file
9
9
  @is_image = is_image
10
+ @is_audio = is_audio
10
11
  @direct_upload = direct_upload
11
12
  @button_size = button_size
12
13
  @resource = resource
@@ -2,6 +2,8 @@
2
2
  <% if @file.present? %>
3
3
  <% if @file.representable? && @is_image %>
4
4
  <%= image_tag helpers.main_app.url_for(@file), class: 'rounded-lg max-h-168 max-w-full' %>
5
+ <% elsif @is_audio %>
6
+ <%= audio_tag(helpers.main_app.url_for(@file), controls: true, preload: false, class: 'w-full')%>
5
7
  <% else %>
6
8
  <div class="relative flex flex-col justify-evenly items-center px-2 rounded-lg border bg-white border-gray-500 min-h-48">
7
9
  <div class="flex flex-col justify-center items-center w-full">
@@ -3,10 +3,11 @@
3
3
  class Avo::Fields::Common::SingleFileViewerComponent < ViewComponent::Base
4
4
  include Avo::ApplicationHelper
5
5
 
6
- def initialize(id:, file:, is_image:, direct_upload: false, resource:, button_size: :md)
6
+ def initialize(id:, file:, is_image:, is_audio:, direct_upload: false, resource:, button_size: :md)
7
7
  @id = id
8
8
  @file = file
9
9
  @is_image = is_image
10
+ @is_audio = is_audio
10
11
  @direct_upload = direct_upload
11
12
  @button_size = button_size
12
13
  @resource = resource
@@ -1,7 +1,7 @@
1
1
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
2
2
  <% if @field.value.present? %>
3
3
  <div class="mb-2">
4
- <%= render Avo::Fields::Common::SingleFileViewerComponent.new resource: @resource, id: @field.id, file: @field.value, is_image: @field.is_image, button_size: :md %>
4
+ <%= render Avo::Fields::Common::SingleFileViewerComponent.new resource: @resource, id: @field.id, file: @field.value, is_image: @field.is_image, is_audio: @field.is_audio, button_size: :md %>
5
5
  </div>
6
6
  <% end %>
7
7
 
@@ -1,7 +1,9 @@
1
1
  <%= index_field_wrapper field: @field do %>
2
2
  <% if @field.value.present? %>
3
3
  <% if @field.value.attached? && @field.value.representable? && @field.is_image %>
4
- <%= link_to_if @field.link_to_resource, image_tag(helpers.main_app.url_for(@field.value), class: 'max-h-full'), resource_path, class: 'block h-8' %>
4
+ <%= link_to_if @field.link_to_resource, image_tag(helpers.main_app.url_for(@field.value), class: 'max-h-full'), resource_path, class: 'block' %>
5
+ <% elsif @field.value.attached? && @field.is_audio %>
6
+ <%= link_to_if @field.link_to_resource, audio_tag(helpers.main_app.url_for(@field.value), controls: true, preload: false, class: 'max-h-full'), resource_path, class: 'block h-8' %>
5
7
  <% else %>
6
8
  <%= @field.value.filename %>
7
9
  <% end %>
@@ -1,3 +1,3 @@
1
1
  <%= show_field_wrapper field: @field do %>
2
- <%= render Avo::Fields::Common::SingleFileViewerComponent.new resource: @resource, id: @field.id, file: @field.value.attachment, is_image: @field.is_image, button_size: :md %>
2
+ <%= render Avo::Fields::Common::SingleFileViewerComponent.new resource: @resource, id: @field.id, file: @field.value.attachment, is_image: @field.is_image, is_audio: @field.is_audio, button_size: :md %>
3
3
  <% end %>
@@ -16,6 +16,7 @@ class Avo::ResourceComponent < ViewComponent::Base
16
16
 
17
17
  if @reflection.present?
18
18
  reflection_resource = ::Avo::App.get_resource_by_model_name(@reflection.active_record.name)
19
+ reflection_resource.hydrate(model: @parent_model) if @parent_model.present?
19
20
  association_name = params['related_name']
20
21
 
21
22
  if association_name.present?
@@ -24,7 +24,7 @@
24
24
  data-selected-resources="[]"
25
25
  >
26
26
  <div class="flex items-center px-6 w-64">
27
- <%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.model_class.model_name.plural} if @resource.search_query.present? %>
27
+ <%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.model_key} if @resource.search_query.present? %>
28
28
  </div>
29
29
  <div class="flex justify-end items-center px-6 space-x-3">
30
30
  <%= render partial: 'avo/partials/view_toggle_button', locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: @turbo_frame } if @models.present? %>
@@ -4,11 +4,11 @@ module Avo
4
4
  class RelationsController < BaseController
5
5
  before_action :set_model, only: [:show, :index, :new, :create, :destroy]
6
6
  before_action :set_related_resource_name
7
- before_action :set_related_resource
8
- before_action :hydrate_related_resource
7
+ before_action :set_related_resource, only: [:show, :index, :new, :create, :destroy]
8
+ before_action :hydrate_related_resource, only: [:show, :index, :new, :create, :destroy]
9
9
  before_action :set_related_model, only: [:show]
10
- before_action :set_attachment_class
11
- before_action :set_attachment_resource
10
+ before_action :set_attachment_class, only: [:show, :index, :new, :create, :destroy]
11
+ before_action :set_attachment_resource, only: [:show, :index, :new, :create, :destroy]
12
12
  before_action :set_attachment_model, only: [:create, :destroy]
13
13
  before_action :set_reflection, only: [:index, :show]
14
14
 
@@ -60,7 +60,6 @@ module Avo
60
60
  _id: model.id,
61
61
  _label: resource.label,
62
62
  _url: resource.record_path,
63
- model: model
64
63
  }
65
64
 
66
65
  if App.license.has_with_trial(:enhanced_search_results)
@@ -1,65 +1,128 @@
1
1
  import { Controller } from 'stimulus'
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ['select', 'type']
4
+ static targets = ['select', 'type', 'loadAssociationLink'];
5
+
6
+ defaults = {};
5
7
 
6
8
  get selectedType() {
7
9
  return this.selectTarget.value
8
10
  }
9
11
 
10
- connect() {
11
- this.setValidNames()
12
- this.changedType()
12
+ get isSearchable() {
13
+ return this.context.scope.element.dataset.searchable === 'true'
13
14
  }
14
15
 
15
- setValidNames() {
16
- this.typeTargets.forEach((target) => {
17
- const { type } = target.dataset
18
- const select = target.querySelector('select')
19
- const name = select.getAttribute('name')
16
+ get association() {
17
+ return this.context.scope.element.dataset.association
18
+ }
20
19
 
21
- select.setAttribute('valid-name', name)
22
- if (this.selectedType !== type) {
23
- select.selectedIndex = 0
24
- }
25
- })
20
+ get associationClass() {
21
+ return this.context.scope.element.dataset.associationClass
26
22
  }
27
23
 
28
- changedType() {
29
- this.hideAllTypeTargets()
30
- this.enableType(this.selectTarget.value)
24
+ connect() {
25
+ this.copyValidNames()
26
+ this.changeType() // Do the initial type change
31
27
  }
32
28
 
33
- hideAllTypeTargets() {
29
+ changeType() {
30
+ this.hideAllTypes()
31
+ this.showType(this.selectTarget.value)
32
+ }
33
+
34
+ // Private
35
+
36
+ hideAllTypes() {
34
37
  this.typeTargets.forEach((target) => {
35
- this.hideTarget(target)
38
+ target.classList.add('hidden')
39
+
36
40
  this.invalidateTarget(target)
37
41
  })
38
42
  }
39
43
 
40
- hideTarget(target) {
41
- target.classList.add('hidden')
42
- }
44
+ /**
45
+ * Used for invalidating select fields when switching between types so they don't automatically override the previous id.
46
+ * Ex: There are two types Article and Project and the Comment has commentable_id 3 and commentable_type: Article
47
+ * When you change the type from Project to Article the Project field will override the commentable_id value
48
+ * because it was rendered later (alphabetical sorting) and the browser will pick that one up.
49
+ * So we go and copy the name attribute to valid-name for all types and then copy it back to name when the user selects it.
50
+ */
43
51
 
44
- invalidateTarget(target) {
45
- const select = target.querySelector('select')
52
+ /**
53
+ * This method does the initial copying from name to valid-name.
54
+ */
55
+ copyValidNames() {
56
+ this.typeTargets.forEach((target) => {
57
+ const { type } = target.dataset
46
58
 
47
- select.setAttribute('name', '')
48
- }
59
+ if (this.isSearchable) {
60
+ const textInput = target.querySelector('input[type="text"]')
61
+ if (textInput) {
62
+ textInput.setAttribute('valid-name', textInput.getAttribute('name'))
63
+ }
49
64
 
50
- validateTarget(target) {
51
- const select = target.querySelector('select')
52
- const validName = select.getAttribute('valid-name')
65
+ const hiddenInput = target.querySelector('input[type="hidden"]')
66
+ if (hiddenInput) {
67
+ hiddenInput.setAttribute(
68
+ 'valid-name',
69
+ hiddenInput.getAttribute('name'),
70
+ )
71
+ }
72
+ } else {
73
+ const select = target.querySelector('select')
74
+ if (select) {
75
+ select.setAttribute('valid-name', select.getAttribute('name'))
76
+ }
53
77
 
54
- select.setAttribute('name', validName)
78
+ if (this.selectedType !== type) {
79
+ select.selectedIndex = 0
80
+ }
81
+ }
82
+ })
55
83
  }
56
84
 
57
- enableType(type) {
58
- const target = this.typeTargets.find((typeTarget) => typeTarget.dataset.type === type)
59
-
85
+ showType(type) {
86
+ const target = this.typeTargets.find(
87
+ (typeTarget) => typeTarget.dataset.type === type,
88
+ )
60
89
  if (target) {
61
90
  target.classList.remove('hidden')
91
+
62
92
  this.validateTarget(target)
63
93
  }
64
94
  }
95
+
96
+ /**
97
+ * Copy value from `valid-name` to `name`
98
+ */
99
+ validateTarget(target) {
100
+ if (this.isSearchable) {
101
+ const textInput = target.querySelector('input[type="text"]')
102
+ const hiddenInput = target.querySelector('input[type="hidden"]')
103
+
104
+ textInput.setAttribute('name', textInput.getAttribute('valid-name'))
105
+ hiddenInput.setAttribute('name', hiddenInput.getAttribute('valid-name'))
106
+ } else {
107
+ const select = target.querySelector('select')
108
+ select.setAttribute('name', select.getAttribute('valid-name'))
109
+ }
110
+ }
111
+
112
+ /**
113
+ * nullify the `name` attribute
114
+ */
115
+ invalidateTarget(target) {
116
+ if (this.isSearchable) {
117
+ // Wrapping it in a try/catch to counter turbo's cache system (going back to the edit page after initial save)
118
+ try {
119
+ target.querySelector('input[type="text"]').setAttribute('name', '')
120
+ target.querySelector('input[type="hidden"]').setAttribute('name', '')
121
+ } catch {}
122
+ } else if (target) {
123
+ try {
124
+ target.querySelector('select').setAttribute('name', '')
125
+ } catch (error) {}
126
+ }
127
+ }
65
128
  }