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.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/Gemfile.lock +5 -3
- data/app/assets/svgs/x.svg +3 -0
- data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +22 -0
- data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +33 -0
- data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +39 -33
- data/app/components/avo/fields/common/files_list_viewer_component.html.erb +1 -1
- data/app/components/avo/fields/common/multiple_file_viewer_component.html.erb +2 -0
- data/app/components/avo/fields/common/multiple_file_viewer_component.rb +2 -1
- data/app/components/avo/fields/common/single_file_viewer_component.html.erb +2 -0
- data/app/components/avo/fields/common/single_file_viewer_component.rb +2 -1
- data/app/components/avo/fields/file_field/edit_component.html.erb +1 -1
- data/app/components/avo/fields/file_field/index_component.html.erb +3 -1
- data/app/components/avo/fields/file_field/show_component.html.erb +1 -1
- data/app/components/avo/resource_component.rb +1 -0
- data/app/components/avo/views/resource_index_component.html.erb +1 -1
- data/app/controllers/avo/relations_controller.rb +4 -4
- data/app/controllers/avo/search_controller.rb +0 -1
- data/app/javascript/js/controllers/fields/belongs_to_field_controller.js +96 -33
- data/app/javascript/js/controllers/search_controller.js +83 -17
- data/app/views/avo/partials/_global_search.html.erb +0 -1
- data/app/views/avo/partials/_javascript.html.erb +1 -0
- data/app/views/avo/partials/_resource_search.html.erb +0 -1
- data/db/factories.rb +4 -0
- data/lib/avo/fields/base_field.rb +1 -1
- data/lib/avo/fields/belongs_to_field.rb +19 -2
- data/lib/avo/fields/file_field.rb +2 -0
- data/lib/avo/fields/files_field.rb +2 -0
- data/lib/avo/licensing/pro_license.rb +2 -1
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/locales/avo.en.yml +1 -0
- data/public/avo-assets/avo.css +16 -0
- data/public/avo-assets/avo.js +317 -234
- data/public/avo-assets/avo.js.map +2 -2
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad998ae9691ee49081421f3db40c867fadf49c16d692722723277b43a631a1f7
|
4
|
+
data.tar.gz: 572ca7ed862e4e8bc3a784ed6c47402b5725ca9e99e286104f70739263e89590
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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.
|
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.
|
432
|
+
puma (~> 5.6.2)
|
431
433
|
pundit
|
432
434
|
rails (~> 6.1.0)
|
433
435
|
rails-controller-testing
|
@@ -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
|
-
<%
|
2
|
-
|
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#
|
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
|
-
|
30
|
-
{
|
31
|
-
|
32
|
-
},
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
},
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
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.
|
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
|
|
@@ -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
|
-
|
11
|
-
this.
|
12
|
-
this.changedType()
|
12
|
+
get isSearchable() {
|
13
|
+
return this.context.scope.element.dataset.searchable === 'true'
|
13
14
|
}
|
14
15
|
|
15
|
-
|
16
|
-
this.
|
17
|
-
|
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
|
-
|
22
|
-
|
23
|
-
select.selectedIndex = 0
|
24
|
-
}
|
25
|
-
})
|
20
|
+
get associationClass() {
|
21
|
+
return this.context.scope.element.dataset.associationClass
|
26
22
|
}
|
27
23
|
|
28
|
-
|
29
|
-
this.
|
30
|
-
this.
|
24
|
+
connect() {
|
25
|
+
this.copyValidNames()
|
26
|
+
this.changeType() // Do the initial type change
|
31
27
|
}
|
32
28
|
|
33
|
-
|
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
|
-
|
38
|
+
target.classList.add('hidden')
|
39
|
+
|
36
40
|
this.invalidateTarget(target)
|
37
41
|
})
|
38
42
|
}
|
39
43
|
|
40
|
-
|
41
|
-
|
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
|
-
|
45
|
-
|
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
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
78
|
+
if (this.selectedType !== type) {
|
79
|
+
select.selectedIndex = 0
|
80
|
+
}
|
81
|
+
}
|
82
|
+
})
|
55
83
|
}
|
56
84
|
|
57
|
-
|
58
|
-
const target = this.typeTargets.find(
|
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
|
}
|