avo 1.22.1.pre.1 → 1.22.1.pre.2

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.

@@ -13,35 +13,35 @@ class Avo::Fields::BelongsToField::AutocompleteComponent < ViewComponent::Base
13
13
  end
14
14
 
15
15
  def field_label
16
- if searchable?
17
- # New records won't have the value (instantiated model) present but the polymorphic_type and polymorphic_id prefilled
18
- if new_record? && has_polymorphic_association?
19
- @polymorphic_record.send(polymorphic_fields[:label])
20
- else
21
- @field.value&.class == @type ? @field.field_label : nil
22
- end
23
- else
24
- @field.field_label
16
+ result = @field.field_label
17
+
18
+ # New records won't have the value (instantiated model) present but the polymorphic_type and polymorphic_id prefilled
19
+ if should_prefill?
20
+ result = @field.value&.class == @type ? @field.field_label : nil
25
21
  end
22
+
23
+ result
26
24
  end
27
25
 
28
26
  def field_value
29
- if searchable?
30
- # New records won't have the value (instantiated model) present but the polymorphic_type and polymorphic_id prefilled
31
- if new_record? && has_polymorphic_association?
32
- @polymorphic_record.send(polymorphic_fields[:id])
33
- else
34
- @field.value&.class == @type ? @field.field_value : nil
35
- end
36
- else
37
- @field.field_value
27
+ result = @field.field_value
28
+
29
+ # New records won't have the value (instantiated model) present but the polymorphic_type and polymorphic_id prefilled
30
+ if should_prefill?
31
+ result = @field.value&.class == @type ? @field.field_value : nil
38
32
  end
33
+
34
+ result
39
35
  end
40
36
 
41
37
  private
42
38
 
39
+ def should_prefill?
40
+ @field.is_polymorphic? && searchable? && !(new_record? && has_polymorphic_association?)
41
+ end
42
+
43
43
  def searchable?
44
- @type.present?
44
+ @field.searchable
45
45
  end
46
46
 
47
47
  def new_record?
@@ -13,7 +13,7 @@
13
13
  data-association-class="<%= @field&.target_resource&.model_class || nil %>"
14
14
  >
15
15
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
16
- <%= @form.select "#{@field.foreign_key}_type", @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
16
+ <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
17
17
  {
18
18
  value: @field.value,
19
19
  include_blank: @field.placeholder,
@@ -29,7 +29,7 @@
29
29
  # If the select field is disabled, no value will be sent. It's how HTML works.
30
30
  # Thus the extra hidden field to actually send the related id to the server.
31
31
  if disabled %>
32
- <%= @form.hidden_field "#{@field.foreign_key}_type" %>
32
+ <%= @form.hidden_field @field.type_input_foreign_key %>
33
33
  <% end %>
34
34
  <% end %>
35
35
  <% @field.types.each do |type| %>
@@ -43,15 +43,16 @@
43
43
  field: @field,
44
44
  type: type,
45
45
  model_key: model_keys[type.to_s],
46
- foreign_key: "#{@field.foreign_key}_id",
46
+ foreign_key: @field.id_input_foreign_key,
47
47
  resource: @resource,
48
48
  disabled: disabled,
49
49
  polymorphic_record: polymorphic_record
50
50
  %>
51
51
  <% else %>
52
- <%= @form.select "#{@field.foreign_key}_id", options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model["#{@field.foreign_key}_id"] : nil),
52
+ <%= @form.select @field.id_input_foreign_key,
53
+ options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
53
54
  {
54
- value: @resource.model["#{@field.foreign_key}_id"].to_s,
55
+ value: @resource.model[@field.id_input_foreign_key].to_s,
55
56
  include_blank: @field.placeholder,
56
57
  },
57
58
  {
@@ -63,7 +64,7 @@
63
64
  # If the select field is disabled, no value will be sent. It's how HTML works.
64
65
  # Thus the extra hidden field to actually send the related id to the server.
65
66
  if disabled %>
66
- <%= @form.hidden_field "#{@field.foreign_key}_id" %>
67
+ <%= @form.hidden_field @field.id_input_foreign_key %>
67
68
  <% end %>
68
69
  <% end %>
69
70
  <% end %>
@@ -76,12 +77,12 @@
76
77
  <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
77
78
  field: @field,
78
79
  model_key: @field.target_resource&.model_key,
79
- foreign_key: @field.foreign_key,
80
+ foreign_key: @field.id_input_foreign_key,
80
81
  resource: @resource,
81
82
  disabled: disabled
82
83
  %>
83
84
  <% else %>
84
- <%= @form.select @field.foreign_key, @field.options.map { |o| [o[:label], o[:value]] },
85
+ <%= @form.select @field.id_input_foreign_key, @field.options,
85
86
  {
86
87
  include_blank: @field.placeholder,
87
88
  value: @field.value
@@ -95,7 +96,7 @@
95
96
  # If the select field is disabled, no value will be sent. It's how HTML works.
96
97
  # Thus the extra hidden field to actually send the related id to the server.
97
98
  if disabled %>
98
- <%= @form.hidden_field @field.foreign_key %>
99
+ <%= @form.hidden_field @field.id_input_foreign_key %>
99
100
  <% end %>
100
101
  <% end %>
101
102
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
2
2
  <%= @form.select @field.id, @field.options_for_select, { selected: @field.value, prompt: @field.placeholder }, {
3
- class: helpers.input_classes(' w-full', has_error: @field.model_errors.include?(@field.id)),
4
- disabled: @field.readonly,
5
- value: @field.model.present? ? @field.model[@field.id] : @field.value } %>
3
+ class: helpers.input_classes(' w-full', has_error: @field.model_errors.include?(@field.id)),
4
+ disabled: @field.readonly,
5
+ value: @field.model.present? ? @field.model[@field.id] : @field.value } %>
6
6
  <% end %>
@@ -84,6 +84,7 @@ module Avo
84
84
  def new
85
85
  @model = @resource.model_class.new
86
86
  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
87
+ # abort @model.course.inspect
87
88
 
88
89
  @page_title = @resource.default_panel_name
89
90
  add_breadcrumb resource_name.humanize, resources_path(resource: @resource)
@@ -37,10 +37,7 @@ module Avo
37
37
  query = @authorization.apply_policy @attachment_class
38
38
 
39
39
  @options = query.all.map do |model|
40
- {
41
- value: model.id,
42
- label: model.send(@attachment_resource.class.title)
43
- }
40
+ [model.send(@attachment_resource.class.title), model.id]
44
41
  end
45
42
  end
46
43
 
@@ -59,6 +59,7 @@ document.addEventListener('turbo:before-fetch-response', (e) => {
59
59
  })
60
60
  document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading'))
61
61
  document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading'))
62
+ document.addEventListener('turbo:submit-end', () => document.body.classList.remove('turbo-loading'))
62
63
  document.addEventListener('turbo:before-cache', () => {
63
64
  document.querySelectorAll('[data-turbo-remove-before-cache]').forEach((element) => element.remove())
64
65
  })
@@ -58,29 +58,16 @@ export default class extends Controller {
58
58
 
59
59
  if (this.isSearchable) {
60
60
  const textInput = target.querySelector('input[type="text"]')
61
- if (textInput) {
62
- textInput.setAttribute('valid-name', textInput.getAttribute('name'))
63
- }
61
+ if (textInput) this.nameToValidName(textInput)
64
62
 
65
63
  const hiddenInput = target.querySelector('input[type="hidden"]')
66
- if (hiddenInput) {
67
- hiddenInput.setAttribute(
68
- 'valid-name',
69
- hiddenInput.getAttribute('name'),
70
- )
71
- }
64
+ if (hiddenInput) this.nameToValidName(hiddenInput)
72
65
  } else {
73
66
  const select = target.querySelector('select')
74
- if (select) {
75
- select.setAttribute('valid-name', select.getAttribute('name'))
76
- }
67
+ if (select) this.nameToValidName(select)
68
+
77
69
  const hiddenInput = target.querySelector('input[type="hidden"]')
78
- if (hiddenInput) {
79
- hiddenInput.setAttribute(
80
- 'valid-name',
81
- hiddenInput.getAttribute('name'),
82
- )
83
- }
70
+ if (hiddenInput) this.nameToValidName(hiddenInput)
84
71
 
85
72
  if (this.selectedType !== type) {
86
73
  select.selectedIndex = 0
@@ -108,13 +95,16 @@ export default class extends Controller {
108
95
  const textInput = target.querySelector('input[type="text"]')
109
96
  const hiddenInput = target.querySelector('input[type="hidden"]')
110
97
 
111
- textInput.setAttribute('name', textInput.getAttribute('valid-name'))
112
- hiddenInput.setAttribute('name', hiddenInput.getAttribute('valid-name'))
98
+ this.validNameToName(textInput)
99
+ this.validNameToName(hiddenInput)
113
100
  } else {
114
101
  const select = target.querySelector('select')
115
102
  const hiddenInput = target.querySelector('input[type="hidden"]')
116
- select.setAttribute('name', select.getAttribute('valid-name'))
117
- hiddenInput.setAttribute('name', hiddenInput.getAttribute('valid-name'))
103
+ this.validNameToName(select)
104
+
105
+ if (hiddenInput) {
106
+ this.validNameToName(hiddenInput)
107
+ }
118
108
  }
119
109
  }
120
110
 
@@ -135,4 +125,12 @@ export default class extends Controller {
135
125
  } catch (error) {}
136
126
  }
137
127
  }
128
+
129
+ validNameToName(target) {
130
+ target.setAttribute('name', target.getAttribute('valid-name'))
131
+ }
132
+
133
+ nameToValidName(target) {
134
+ target.setAttribute('valid-name', target.getAttribute('name'))
135
+ }
138
136
  }
@@ -3,7 +3,7 @@
3
3
  # When rendering the frames the flashed content gets lost.
4
4
  # By including the alerts partial, the stimulus will pick them up and display them to the user.
5
5
  %>
6
- <%= render partial: 'avo/partials/alerts'if flash.present? if flash.present? %>
6
+ <%= render partial: 'avo/partials/alerts' if flash.present? && name.present? %>
7
7
 
8
8
  <%= yield %>
9
9
  <% if name.present? %></turbo-frame><% end %>
@@ -11,7 +11,7 @@
11
11
 
12
12
  <div class="flex-1 flex items-center justify-center px-8 text-lg mt-8 mb-12">
13
13
  <div class="flex-1 flex flex-col items-center justify-center px-24 text-base">
14
- <%= form.select :related_id, options_for_select(@options.map { |o| [o[:label], o[:value]] }, nil),
14
+ <%= form.select :related_id, options_for_select(@options, nil),
15
15
  {
16
16
  include_blank: t('avo.choose_an_option'),
17
17
  },
data/db/factories.rb CHANGED
@@ -17,7 +17,7 @@ FactoryBot.define do
17
17
  end
18
18
 
19
19
  factory :post do
20
- name { Faker::Quote.unique.famous_last_words }
20
+ name { Faker::Quote.famous_last_words }
21
21
  body { Faker::Lorem.paragraphs(number: rand(4...10)).join("\n") }
22
22
  is_featured { [true, false].sample }
23
23
  published_at do
@@ -42,11 +42,11 @@ FactoryBot.define do
42
42
  end
43
43
 
44
44
  factory :comment do
45
- body { Faker::Lorem.paragraphs(number: rand(4...10)).join("\n") }
45
+ body { Faker::Lorem.paragraphs(number: rand(4...10)) }
46
46
  end
47
47
 
48
48
  factory :review do
49
- body { Faker::Lorem.paragraphs(number: rand(4...10)).join("\n") }
49
+ body { Faker::Lorem.paragraphs(number: rand(4...10)) }
50
50
  end
51
51
 
52
52
  factory :person do
@@ -128,11 +128,14 @@ module Avo
128
128
  # Get model value
129
129
  final_value = @model.send(property) if (model_or_class(@model) == "model") && @model.respond_to?(property)
130
130
 
131
+ # On new views and actions modals we need to prefill the fields
131
132
  if (@view === :new) || @action.present?
132
- final_value = if default.present? && default.respond_to?(:call)
133
- default.call
134
- else
135
- default
133
+ if default.present?
134
+ final_value = if default.respond_to?(:call)
135
+ default.call
136
+ else
137
+ default
138
+ end
136
139
  end
137
140
  end
138
141
 
@@ -1,5 +1,62 @@
1
1
  module Avo
2
2
  module Fields
3
+
4
+ # The field can be in multiple scenarios where it needs different types of data and displays the state differently.
5
+ # For example the non-polymorphic, non-searchable variant is the easiest to support. You only need to populate a simple select with the ID of the associated record and the list of records.
6
+ # For the searchable polymorphic variant you need to provide the type of the association (Post, Project, Team), the label of the associated record ("Cool post title") and the ID of that record.
7
+ # Furthermore, the way Avo works, it needs to do some queries on the back-end to fetch the required information.
8
+ #
9
+ # Field scenarios:
10
+ # 1. Create new record
11
+ # List of records
12
+ # 2. Create new record as association
13
+ # List of records, the ID
14
+ # 3. Create new searchable record
15
+ # Nothing really. The records will be fetched from the search API
16
+ # 4. Create new searchable record as association
17
+ # The associated record label and ID. The records will be fetched from the search API
18
+ # 5. Create new polymorphic record
19
+ # Type & ID
20
+ # 6. Create new polymorphic record as association
21
+ # Type, list of records, and ID
22
+ # 7. Create new polymorphic searchable record
23
+ # Type, Label and ID
24
+ # 8. Create new polymorphic searchable record as association
25
+ # Type, Label and ID
26
+ # 9. Edit a record
27
+ # List of records & ID
28
+ # 10. Edit a record as searchable
29
+ # Label and ID
30
+ # 11. Edit a record as an association
31
+ # List and ID
32
+ # 12. Edit a record as an searchable association
33
+ # Label and ID
34
+ # 13. Edit a polymorphic record
35
+ # Type, List of records & ID
36
+ # 14. Edit a polymorphic record as searchable
37
+ # Type, Label and ID
38
+ # 15. Edit a polymorphic record as an association
39
+ # Type, List and ID
40
+ # 16. Edit a polymorphic record as an searchable association
41
+ # Type, Label and ID
42
+ # Also all of the above with a namespaced model `Course/Link`
43
+
44
+ # Variants
45
+ # 1. Select belongs to
46
+ # 2. Searchable belongs to
47
+ # 3. Select Polymorphic belongs to
48
+ # 4. Searchable Polymorphic belongs to
49
+
50
+ # Requirements
51
+ # - list
52
+ # - ID
53
+ # - label
54
+ # - Type
55
+ # - foreign_key
56
+ # - foreign_key for poly type
57
+ # - foreign_key for poly id
58
+ # - is_disabled?
59
+
3
60
  class BelongsToField < BaseField
4
61
  attr_reader :polymorphic_as
5
62
  attr_reader :relation_method
@@ -21,7 +78,13 @@ module Avo
21
78
  end
22
79
 
23
80
  def value
24
- super(polymorphic_as)
81
+ if is_polymorphic?
82
+ # Get the value from the pre-filled assoociation record
83
+ super(polymorphic_as)
84
+ else
85
+ # Get the value from the pre-filled assoociation record
86
+ super(relation_method)
87
+ end
25
88
  end
26
89
 
27
90
  # The value
@@ -39,22 +102,40 @@ module Avo
39
102
  end
40
103
 
41
104
  def options
42
- ::Avo::Services::AuthorizationService.apply_policy(user, target_resource.class.query_scope).all.map do |model|
43
- {
44
- value: model.id,
45
- label: model.send(target_resource.class.title)
46
- }
47
- end
105
+ values_for_type
48
106
  end
49
107
 
50
- def values_for_type(type)
51
- ::Avo::Services::AuthorizationService.apply_policy(user, type).all.map do |model|
52
- [model.send(App.get_resource_by_model_name(type).class.title), model.id]
108
+ def values_for_type(model = nil)
109
+ resource = target_resource
110
+ resource = App.get_resource_by_model_name model if model.present?
111
+
112
+ ::Avo::Services::AuthorizationService.apply_policy(user, resource.class.query_scope).all.map do |model|
113
+ [model.send(resource.class.title), model.id]
53
114
  end
54
115
  end
55
116
 
56
117
  def database_value
57
118
  target_resource.id
119
+ rescue
120
+ nil
121
+ end
122
+
123
+ def type_input_foreign_key
124
+ if is_polymorphic?
125
+ "#{foreign_key}_type"
126
+ end
127
+ end
128
+
129
+ def id_input_foreign_key
130
+ if is_polymorphic?
131
+ "#{foreign_key}_id"
132
+ else
133
+ foreign_key
134
+ end
135
+ end
136
+
137
+ def is_polymorphic?
138
+ polymorphic_as.present?
58
139
  end
59
140
 
60
141
  def foreign_key
@@ -118,7 +199,7 @@ module Avo
118
199
  end
119
200
 
120
201
  def target_resource
121
- if polymorphic_as.present?
202
+ if is_polymorphic?
122
203
  if value.present?
123
204
  return App.get_resource_by_model_name(value.class)
124
205
  else
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "1.22.1.pre.1"
2
+ VERSION = "1.22.1.pre.2"
3
3
  end
@@ -81101,22 +81101,18 @@
81101
81101
  const { type } = target.dataset;
81102
81102
  if (this.isSearchable) {
81103
81103
  const textInput = target.querySelector('input[type="text"]');
81104
- if (textInput) {
81105
- textInput.setAttribute("valid-name", textInput.getAttribute("name"));
81106
- }
81104
+ if (textInput)
81105
+ this.nameToValidName(textInput);
81107
81106
  const hiddenInput = target.querySelector('input[type="hidden"]');
81108
- if (hiddenInput) {
81109
- hiddenInput.setAttribute("valid-name", hiddenInput.getAttribute("name"));
81110
- }
81107
+ if (hiddenInput)
81108
+ this.nameToValidName(hiddenInput);
81111
81109
  } else {
81112
81110
  const select = target.querySelector("select");
81113
- if (select) {
81114
- select.setAttribute("valid-name", select.getAttribute("name"));
81115
- }
81111
+ if (select)
81112
+ this.nameToValidName(select);
81116
81113
  const hiddenInput = target.querySelector('input[type="hidden"]');
81117
- if (hiddenInput) {
81118
- hiddenInput.setAttribute("valid-name", hiddenInput.getAttribute("name"));
81119
- }
81114
+ if (hiddenInput)
81115
+ this.nameToValidName(hiddenInput);
81120
81116
  if (this.selectedType !== type) {
81121
81117
  select.selectedIndex = 0;
81122
81118
  }
@@ -81134,13 +81130,15 @@
81134
81130
  if (this.isSearchable) {
81135
81131
  const textInput = target.querySelector('input[type="text"]');
81136
81132
  const hiddenInput = target.querySelector('input[type="hidden"]');
81137
- textInput.setAttribute("name", textInput.getAttribute("valid-name"));
81138
- hiddenInput.setAttribute("name", hiddenInput.getAttribute("valid-name"));
81133
+ this.validNameToName(textInput);
81134
+ this.validNameToName(hiddenInput);
81139
81135
  } else {
81140
81136
  const select = target.querySelector("select");
81141
81137
  const hiddenInput = target.querySelector('input[type="hidden"]');
81142
- select.setAttribute("name", select.getAttribute("valid-name"));
81143
- hiddenInput.setAttribute("name", hiddenInput.getAttribute("valid-name"));
81138
+ this.validNameToName(select);
81139
+ if (hiddenInput) {
81140
+ this.validNameToName(hiddenInput);
81141
+ }
81144
81142
  }
81145
81143
  }
81146
81144
  invalidateTarget(target) {
@@ -81158,6 +81156,12 @@
81158
81156
  }
81159
81157
  }
81160
81158
  }
81159
+ validNameToName(target) {
81160
+ target.setAttribute("name", target.getAttribute("valid-name"));
81161
+ }
81162
+ nameToValidName(target) {
81163
+ target.setAttribute("valid-name", target.getAttribute("name"));
81164
+ }
81161
81165
  };
81162
81166
  __publicField(belongs_to_field_controller_default, "targets", ["select", "type", "loadAssociationLink"]);
81163
81167
 
@@ -87796,6 +87800,7 @@
87796
87800
  });
87797
87801
  document.addEventListener("turbo:visit", () => document.body.classList.add("turbo-loading"));
87798
87802
  document.addEventListener("turbo:submit-start", () => document.body.classList.add("turbo-loading"));
87803
+ document.addEventListener("turbo:submit-end", () => document.body.classList.remove("turbo-loading"));
87799
87804
  document.addEventListener("turbo:before-cache", () => {
87800
87805
  document.querySelectorAll("[data-turbo-remove-before-cache]").forEach((element) => element.remove());
87801
87806
  });