-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add address multi-field partials, add support for self-refreshing for…
…m fields (#86) * add addresses/countries.json and addresses/states.json * add Addresses::Country, Addresses::Region models, concerns * add Address model, Addresses::Base concern * add address_field helpers to popuplate country, region options * add addresses locale * add address_field * add address field to tangible_things * permit address field for tangible things * add admin_divisions to certain countries * add locale strings for admin_divisions, zip_code * adapt region name and postal code field labels per country * switch tangible_things to default to Japan * super-select: allow setting more wrapper_options * add refresh_fields_helper * add dependable_controller.js, refresh_fields_controller.js * address_field: update region, postal code fields on country change * refresh_fields_helper: clean up to_sym * address_field: allow nil country_id * address_field: restore previous field values after dependent fields refreshed * refresh_fields: toggle loading styling, disable fields while loading * refresh_fields: ensure super-select validates new value on restoreFieldValues * refactor refresh_fields_controller * fix Address model class * address_field: lay out in three columns on desktop: city, state, zip * address: rely on list of country_ids for knowing how to call admin divisions * tangible_things: :address => :address_value * bullet_train-fields: add snail gem * add address_formatted using snail gem * add address attributes partial * add address attribute to tangible_things/show * add all_blank? check to records/base for checking if address is blank * override Address.all_blank? with custom check * only show address attribute (and label) if not all_blank? * tangible_thing: provide address to edit form if missing * fix Address.all_blank? * replace address_formatted with modified code from snail gem * address_formatted: tweaks * remove snail gem * addresses.en.yml: remove default country, region options * add Addresses::Continent and Addresses::Subcontinent models * address_field_helper: add notes about different lookup approaches * address_city_line_formatted: clarify cleanup steps * Fixing Standard Ruby. * add addresses/countries.json and addresses/states.json * add Addresses::Country, Addresses::Region models, concerns * add Address model, Addresses::Base concern * add address_field helpers to popuplate country, region options * add addresses locale * add address_field * add address field to tangible_things * permit address field for tangible things * add admin_divisions to certain countries * add locale strings for admin_divisions, zip_code * adapt region name and postal code field labels per country * switch tangible_things to default to Japan * super-select: allow setting more wrapper_options * add refresh_fields_helper * add dependable_controller.js, refresh_fields_controller.js * address_field: update region, postal code fields on country change * refresh_fields_helper: clean up to_sym * address_field: allow nil country_id * address_field: restore previous field values after dependent fields refreshed * refresh_fields: toggle loading styling, disable fields while loading * refresh_fields: ensure super-select validates new value on restoreFieldValues * refactor refresh_fields_controller * fix Address model class * address_field: lay out in three columns on desktop: city, state, zip * address: rely on list of country_ids for knowing how to call admin divisions * tangible_things: :address => :address_value * bullet_train-fields: add snail gem * add address_formatted using snail gem * add address attributes partial * add address attribute to tangible_things/show * add all_blank? check to records/base for checking if address is blank * override Address.all_blank? with custom check * only show address attribute (and label) if not all_blank? * tangible_thing: provide address to edit form if missing * fix Address.all_blank? * replace address_formatted with modified code from snail gem * address_formatted: tweaks * remove snail gem * addresses.en.yml: remove default country, region options * add Addresses::Continent and Addresses::Subcontinent models * address_field_helper: add notes about different lookup approaches * address_city_line_formatted: clarify cleanup steps * Fixing Standard Ruby. * empty commit for CI * remove duplicate all_blank? method * re-remove to_api_json * moar declarative --------- Co-authored-by: Andrew Culver <andrew.culver@gmail.com> Co-authored-by: Jeremy Green <jeremy@octolabs.com>
- Loading branch information
1 parent
eb1758b
commit 4f79050
Showing
29 changed files
with
55,431 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -215,6 +215,7 @@ PLATFORMS | |
arm64-darwin-22 | ||
ruby | ||
x86_64-darwin-21 | ||
x86_64-darwin-22 | ||
x86_64-linux | ||
|
||
DEPENDENCIES | ||
|
127 changes: 127 additions & 0 deletions
127
bullet_train-fields/app/helpers/fields/address_field_helper.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
module Fields::AddressFieldHelper | ||
def populate_country_options | ||
([Addresses::Country.find_by(name: "United States"), Addresses::Country.find_by(name: "Canada")] + Addresses::Country.all).map { |country| [country.name, country.id] }.uniq | ||
end | ||
|
||
def populate_region_options(address_form) | ||
return [] if address_form.object.country_id.nil? | ||
Addresses::Region.where(country_id: address_form.object.country_id).map { |region| [region.name, region.id] } | ||
end | ||
|
||
def admin_division_label_for(address_form) | ||
# using country_id because it's fastest, even if this case statement is hard to read | ||
admin_divisions_key = case address_form.object.country_id | ||
when 233, 31, 142, 239, 101 | ||
:states | ||
when 39 | ||
:provinces_territories | ||
when 109 | ||
:prefectures | ||
when 107, 45, 116, 182, 207, 219, 230, 156, 204 | ||
:provinces | ||
when 14 | ||
:states_territories | ||
when 59 | ||
:regions | ||
when 82, 15 | ||
:federal_states | ||
when 75 | ||
:departments | ||
when 105 | ||
:counties | ||
when 214 | ||
:cantons | ||
else | ||
:default | ||
end | ||
path = [:addresses, :fields, :admin_divisions, admin_divisions_key] | ||
t(path.compact.join(".")) | ||
end | ||
|
||
def postal_code_label_for(address_form) | ||
key = if address_form.object.country_id == 233 | ||
:zip_code | ||
else | ||
:postal_code | ||
end | ||
path = [:addresses, :fields, key, :label] | ||
t(path.compact.join(".")) | ||
end | ||
|
||
def address_formatted(address, one_line: false) | ||
return "" if address.all_blank? | ||
|
||
formatted = [ | ||
address.address_one, | ||
address.address_two, | ||
address_city_line_formatted(address), | ||
address.country&.name&.upcase | ||
].reject(&:blank?) | ||
|
||
if one_line | ||
formatted.join(", ") # simplistic | ||
else | ||
simple_format(formatted.join("\n")) | ||
end | ||
end | ||
|
||
def address_city_line_formatted(address) | ||
country_iso2 = address.country&.iso2 # can be nil or empty | ||
city = address.city | ||
region = address.region&.name | ||
postal_code = address.postal_code | ||
|
||
# adapted from https://github.com/cainlevy/snail/blob/master/lib/snail.rb | ||
# using iso2 property here because it's a port of what's used in snail gem | ||
# will be cleaned up below if parts missing | ||
formatted = case country_iso2 | ||
when "CN", "IN" | ||
"#{city}, #{region} #{postal_code}" | ||
when "BR" | ||
"#{postal_code} #{city}#{"-" unless city.nil? || city.empty? || region.nil? || region.empty?}#{region}" | ||
when "MX", "SK" | ||
"#{postal_code} #{city}, #{region}" | ||
when "IT" | ||
"#{postal_code} #{city} #{region}" | ||
when "BY" | ||
"#{postal_code} #{city}#{"-" unless city.nil? || city.empty? || region.nil? || region.empty?}#{region}" | ||
when "US", "CA", "AU", nil, "" | ||
"#{city} #{region} #{postal_code}" | ||
when "IL", "DK", "FI", "FR", "DE", "GR", "NO", "ES", "SE", "TR", "CY", "PT", "MK", "BA" | ||
"#{postal_code} #{city}" | ||
when "KW", "SY", "OM", "EE", "LU", "BE", "IS", "CH", "AT", "MD", "ME", "RS", "BG", "GE", "PL", "AM", "HR", "RO", "AZ" | ||
"#{postal_code} #{city}" | ||
when "NL" | ||
"#{postal_code} #{city}" | ||
when "IE" | ||
"#{city}, #{region}\n#{postal_code}" | ||
when "GB", "RU", "UA", "JO", "LB", "IR", "SA", "NZ" | ||
"#{city} #{postal_code}" # Locally these may be on separate lines. The USPS prefers the city line above the country line, though. | ||
when "EC" | ||
"#{postal_code} #{city}" | ||
when "HK", "IQ", "YE", "QA", "AL" | ||
city.to_s | ||
when "AE" | ||
"#{postal_code}\n#{city}" | ||
when "JP" | ||
"#{city}, #{region}\n#{postal_code}" | ||
when "EG", "ZA", "IM", "KZ", "HU" | ||
"#{city}\n#{postal_code}" | ||
when "LV" | ||
"#{city}, LV-#{postal_code}".gsub(/LV-\s*$/, "") # undo if no postal code | ||
when "LT" | ||
"LT-#{postal_code} #{city}".gsub(/^LT-\s*/, "") # undo if no postal code | ||
when "SI" | ||
"SI-#{postal_code} #{city}".gsub(/^SI-\s*/, "") # undo if no postal code | ||
when "CZ" | ||
"#{postal_code} #{region}\n#{city}" | ||
else | ||
"#{city} #{region} #{postal_code}" | ||
end | ||
|
||
# clean up separators when missing pieces | ||
formatted.strip # remove extra spaces and newlines before and after | ||
.gsub(/^,\s*/, "") # remove extra comma from start | ||
.gsub(/\s*,$/, "") # remove extra comma from end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
module RefreshFieldsHelper | ||
def accept_query_string_override_for(form, method) | ||
field_name = form.field_name(method) | ||
|
||
new_value = new_value_from_query_string(field_name) | ||
return if new_value.nil? | ||
|
||
form.object[method] = new_value | ||
end | ||
|
||
private | ||
|
||
def new_value_from_query_string(field_name) | ||
params.dig(*params_dig_path_for_field_name(field_name)) | ||
end | ||
|
||
def params_dig_path_for_field_name(field_name) | ||
dig_path = [] | ||
|
||
nested_keys = Rack::Utils.parse_nested_query(field_name) | ||
|
||
while !nested_keys.nil? && nested_keys.keys.size | ||
key = nested_keys.keys.first | ||
dig_path << key.to_sym | ||
nested_keys = nested_keys[key] | ||
end | ||
|
||
dig_path | ||
end | ||
end |
24 changes: 24 additions & 0 deletions
24
bullet_train-fields/app/javascript/controllers/dependable_controller.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
|
||
export default class extends Controller { | ||
static values = { | ||
dependentsSelector: String | ||
} | ||
|
||
updateDependents(event) { | ||
if (!this.hasDependents) { return false } | ||
|
||
this.dependents.forEach((dependent) => { | ||
dependent.dispatchEvent(new CustomEvent(`${this.identifier}:updated`, { detail: { event: event }, bubbles: true, cancelable: false })) | ||
}) | ||
} | ||
|
||
get hasDependents() { | ||
return (this.dependents.length > 0) | ||
} | ||
|
||
get dependents() { | ||
if (!this.dependentsSelectorValue) { return [] } | ||
return document.querySelectorAll(this.dependentsSelectorValue) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
bullet_train-fields/app/javascript/controllers/refresh_fields_controller.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
|
||
export default class extends Controller { | ||
static targets = [ "field" ] | ||
static values = { | ||
valuesStore: { type: Object, default: {} }, | ||
loading: { type: Boolean, default: false } | ||
} | ||
static classes = [ "loading" ] | ||
|
||
updateFrameFromDependentField(event) { | ||
const field = event?.detail?.event?.detail?.event?.target || // super select nests its original jQuery event, contains <select> target | ||
event?.detail?.event?.target || // dependable_controller will include the original event in detail | ||
event?.target // maybe it was fired straight from the field | ||
|
||
if (!field) { return } | ||
|
||
this.storeFieldValues() | ||
|
||
this.loadingValue = true | ||
this.disableFieldInputWhileRefreshing() | ||
|
||
const frame = this.element | ||
frame.src = this.constructNewUrlUpdatingField(field.name, field.value) | ||
} | ||
|
||
finishFrameUpdate(event) { | ||
if (event.target !== this.element) { return } | ||
|
||
this.restoreFieldValues() | ||
this.loadingValue = false | ||
} | ||
|
||
constructNewUrlUpdatingField(fieldName, fieldValue) { | ||
const url = new URL(window.location.href) | ||
url.searchParams.set(fieldName, fieldValue) | ||
|
||
return url.href | ||
} | ||
|
||
disableFieldInputWhileRefreshing() { | ||
this.fieldTargets.forEach(field => field.disabled = true ) | ||
} | ||
|
||
loadingValueChanged() { | ||
if (!this.hasLoadingClass) { return } | ||
this.element.classList.toggle(...this.loadingClasses, this.loadingValue) | ||
} | ||
|
||
storeFieldValues() { | ||
this.fieldTargets.forEach(field => { | ||
let storeUpdate = {} | ||
storeUpdate[field.name] = field.value | ||
this.valuesStoreValue = Object.assign(this.valuesStoreValue, storeUpdate) | ||
}) | ||
} | ||
|
||
restoreFieldValues() { | ||
this.fieldTargets.forEach(field => { | ||
const storedValue = this.valuesStoreValue[field.name] | ||
if (storedValue === undefined) { return } | ||
field.value = storedValue | ||
field.dispatchEvent(new Event('change')) // ensures cascading effects, including super-select validating against valid options | ||
}) | ||
|
||
this.valuesStoreValue = {} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.