Skip to content

Commit 6056a68

Browse files
committed
Refactor address form component (properly this time)
Break form into View Component slots, with more granular rendering control of each field. Form composition is decided at initialization, where user can provide `fields_preset` option (:contact by default) which then will pick relevant fields for given form. I.e. fields :name, :vat_id, :reverse_charge_status are relevant only for "contact" address type (e.g. customers, companies), while not relevant for "location" address type (e.g. stock locations). Additionally, user can specify further which fields to include or exclude for even more customization, e.g. stock location model does not have `email` attribute yet, if we were to release new stock location form first and then migrate model to add `email` column, we can do that by specifying `exclude_columns: :email` when rendering stock location form and remove it when migration is in place.
1 parent 61f675e commit 6056a68

File tree

6 files changed

+146
-60
lines changed

6 files changed

+146
-60
lines changed
Lines changed: 10 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,22 @@
11
<fieldset class="<%= stimulus_id %>"
2-
data-controller="<%= stimulus_id %>"
32
<%= :disabled if @disabled %>
43
>
54
<div class="<%= stimulus_id %>--address-form flex flex-wrap gap-4 pb-4">
6-
<%= render component("ui/forms/field").text_field(@form_field_name, :name, object: @addressable) if @include_name_field %>
7-
<%= render component("ui/forms/field").text_field(@form_field_name, :address1, object: @addressable) %>
8-
<%= render component("ui/forms/field").text_field(@form_field_name, :address2, object: @addressable) %>
5+
<%= name %>
6+
<%= street %>
7+
<%= street_contd %>
98
<div class="flex gap-4 w-full">
10-
<%= render component("ui/forms/field").text_field(@form_field_name, :city, object: @addressable) %>
11-
<%= render component("ui/forms/field").text_field(@form_field_name, :zipcode, object: @addressable) %>
9+
<%= city %>
10+
<%= zipcode %>
1211
</div>
1312

14-
<%= render component("ui/forms/field").select(
15-
@form_field_name,
16-
:country_id,
17-
Spree::Country.all.map { |c| [c.name, c.id] },
18-
object: @addressable,
19-
value: @addressable.try(:country_id),
20-
"data-#{stimulus_id}-target": "country",
21-
"data-action": "change->#{stimulus_id}#loadStates"
22-
) %>
13+
<%= country_and_state %>
2314

24-
<%= content_tag(:div,
25-
data: { "#{stimulus_id}-target": "stateNameWrapper" },
26-
class: (@addressable.country&.states&.empty? ? "flex flex-col gap-2 w-full" : "hidden flex flex-col gap-2 w-full")
27-
) do %>
28-
<%= render component("ui/forms/field").text_field(
29-
@form_field_name,
30-
:state_name,
31-
object: @addressable,
32-
value: @addressable.try(:state_name),
33-
"data-#{stimulus_id}-target": "stateName"
34-
) %>
35-
<% end %>
36-
<input autocomplete="off" type="hidden" name=<%= "#{@form_field_name}[state_id]" %>>
37-
38-
<%= content_tag(:div,
39-
data: { "#{stimulus_id}-target": "stateWrapper" },
40-
class: (@addressable.country&.states&.empty? ? "hidden flex flex-col gap-2 w-full" : "flex flex-col gap-2 w-full")
41-
) do %>
42-
<%= render component("ui/forms/field").select(
43-
@form_field_name,
44-
:state_id,
45-
state_options,
46-
object: @addressable,
47-
value: @addressable.try(:state_id),
48-
"data-#{stimulus_id}-target": "state"
49-
) %>
50-
<% end %>
51-
52-
<%= render component("ui/forms/field").text_field(@form_field_name, :phone, object: @addressable) %>
53-
<%= render component("ui/forms/field").text_field(@form_field_name, :email, object: @addressable) %>
15+
<%= phone %>
16+
<%= email %>
5417
<% if Spree::Backend::Config.show_reverse_charge_fields %>
55-
<%= render component("ui/forms/field").text_field(@form_field_name, :vat_id, object: @addressable) %>
56-
<%= render component("ui/forms/field").select(
57-
@form_field_name,
58-
:reverse_charge_status,
59-
Spree::Address.reverse_charge_statuses.keys.map { |key| [I18n.t("spree.reverse_charge_statuses.#{key}"), key] },
60-
object: @addressable
61-
) %>
18+
<%= vat_id %>
19+
<%= reverse_charge_status %>
6220
<% end %>
6321
</div>
6422
</fieldset>
Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,83 @@
11
# frozen_string_literal: true
22

33
class SolidusAdmin::UI::Forms::Address::Component < SolidusAdmin::BaseComponent
4-
def initialize(addressable:, form_field_name:, disabled: false, include_name_field: true)
4+
DEFAULT_FIELDS = %i[
5+
street
6+
street_contd
7+
city
8+
zipcode
9+
country_and_state
10+
phone
11+
email
12+
].freeze
13+
14+
FIELDS_PRESETS = {
15+
contact: DEFAULT_FIELDS + %i[name vat_id reverse_charge_status],
16+
location: DEFAULT_FIELDS,
17+
}.freeze
18+
19+
renders_one :name, -> do
20+
component("ui/forms/field").text_field(@form_field_name, :name, object: @addressable)
21+
end
22+
23+
renders_one :street, -> do
24+
component("ui/forms/field").text_field(@form_field_name, :address1, object: @addressable)
25+
end
26+
27+
renders_one :street_contd, -> do
28+
component("ui/forms/field").text_field(@form_field_name, :address2, object: @addressable)
29+
end
30+
31+
renders_one :city, -> do
32+
component("ui/forms/field").text_field(@form_field_name, :city, object: @addressable)
33+
end
34+
35+
renders_one :zipcode, -> do
36+
component("ui/forms/field").text_field(@form_field_name, :zipcode, object: @addressable)
37+
end
38+
39+
renders_one :country_and_state, SolidusAdmin::UI::Forms::Address::CountryAndState::Component
40+
renders_one :phone, -> do
41+
component("ui/forms/field").text_field(@form_field_name, :phone, object: @addressable)
42+
end
43+
44+
renders_one :email, -> do
45+
component("ui/forms/field").text_field(@form_field_name, :email, object: @addressable)
46+
end
47+
48+
renders_one :vat_id, -> do
49+
component("ui/forms/field").text_field(@form_field_name, :vat_id, object: @addressable)
50+
end
51+
52+
renders_one :reverse_charge_status, -> do
53+
component("ui/forms/field").select(
54+
@form_field_name,
55+
:reverse_charge_status,
56+
Spree::Address.reverse_charge_statuses.keys.map { |key| [I18n.t("spree.reverse_charge_statuses.#{key}"), key] },
57+
object: @addressable
58+
)
59+
end
60+
61+
# @param fields_preset [Symbol] decides which set of fields to render, accepted values: [:contact, :location]
62+
# @param include_fields [Symbol] optionally include fields that are not present in default/chosen field preset
63+
# @param exclude_fields [Symbol] optionally exclude fields that are present in default/chosen field preset
64+
def initialize(addressable:, form_field_name:, disabled: false, fields_preset: :contact, include_fields: [], exclude_fields: [])
565
@addressable = addressable
666
@form_field_name = form_field_name
767
@disabled = disabled
8-
@include_name_field = include_name_field
9-
end
1068

11-
def state_options
12-
return [] unless @addressable.country
13-
@addressable.country.states.map { |s| [s.name, s.id] }
69+
fields = FIELDS_PRESETS[fields_preset] || []
70+
fields = (fields + Array.wrap(include_fields.map(&:to_sym)) - Array.wrap(exclude_fields.map(&:to_sym))).uniq
71+
72+
with_name if fields.include?(:name)
73+
with_street if fields.include?(:street)
74+
with_street_contd if fields.include?(:street_contd)
75+
with_city if fields.include?(:city)
76+
with_zipcode if fields.include?(:zipcode)
77+
with_country_and_state(addressable:, form_field_name:) if fields.include?(:country_and_state)
78+
with_phone if fields.include?(:phone)
79+
with_email if fields.include?(:email)
80+
with_vat_id if fields.include?(:vat_id)
81+
with_reverse_charge_status if fields.include?(:reverse_charge_status)
1482
end
1583
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<div class="flex flex-col gap-4 w-full" data-controller="<%= stimulus_id %>">
2+
<%= render component("ui/forms/field").select(
3+
@form_field_name,
4+
:country_id,
5+
Spree::Country.all.map { |c| [c.name, c.id] },
6+
object: @addressable,
7+
value: @addressable.try(:country_id),
8+
"data-#{stimulus_id}-target": "country",
9+
"data-action": "change->#{stimulus_id}#loadStates"
10+
) %>
11+
12+
<%= content_tag(:div,
13+
data: { "#{stimulus_id}-target": "stateNameWrapper" },
14+
class: (@addressable.country&.states&.empty? ? "flex flex-col gap-2 w-full" : "hidden flex flex-col gap-2 w-full")
15+
) do %>
16+
<%= render component("ui/forms/field").text_field(
17+
@form_field_name,
18+
:state_name,
19+
object: @addressable,
20+
value: @addressable.try(:state_name),
21+
"data-#{stimulus_id}-target": "stateName"
22+
) %>
23+
<% end %>
24+
<input autocomplete="off" type="hidden" name=<%= "#{@form_field_name}[state_id]" %>>
25+
26+
<%= content_tag(:div,
27+
data: { "#{stimulus_id}-target": "stateWrapper" },
28+
class: (@addressable.country&.states&.empty? ? "hidden flex flex-col gap-2 w-full" : "flex flex-col gap-2 w-full")
29+
) do %>
30+
<%= render component("ui/forms/field").select(
31+
@form_field_name,
32+
:state_id,
33+
state_options,
34+
object: @addressable,
35+
value: @addressable.try(:state_id),
36+
"data-#{stimulus_id}-target": "state"
37+
) %>
38+
<% end %>
39+
</div>

admin/app/components/solidus_admin/ui/forms/address/component.js renamed to admin/app/components/solidus_admin/ui/forms/address/country_and_state/component.js

File renamed without changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
class SolidusAdmin::UI::Forms::Address::CountryAndState::Component < SolidusAdmin::BaseComponent
4+
def initialize(addressable:, form_field_name:)
5+
@addressable = addressable
6+
@form_field_name = form_field_name
7+
end
8+
9+
private
10+
11+
def state_options
12+
return [] unless @addressable.country
13+
@addressable.country.states.map { |s| [s.name, s.id] }
14+
end
15+
end

admin/spec/components/previews/solidus_admin/ui/forms/address/component_preview.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ def overview
99
end
1010

1111
# @param disabled toggle
12-
def playground(disabled: false)
12+
# @param fields_preset select { choices: [contact, location] }
13+
# @param include_fields text "E.g. zipcode,street"
14+
# @param exclude_fields text "E.g. name,street_contd"
15+
def playground(disabled: false, fields_preset: :contact, include_fields: "", exclude_fields: "")
1316
render component("ui/forms/address").new(
1417
form_field_name: "",
1518
addressable: fake_address,
16-
disabled:
19+
disabled:,
20+
fields_preset:,
21+
include_fields: include_fields.present? ? include_fields.gsub(/\s+/, "").split(",") : [],
22+
exclude_fields: exclude_fields.present? ? exclude_fields.gsub(/\s+/, "").split(",") : [],
1723
)
1824
end
1925

0 commit comments

Comments
 (0)