Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address field: Add super-scaffolding support, document new field and document updating dependent-fields in a form #534

Merged
merged 30 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9261497
start adding address_field to super-scaffolding
pascallaliberte Sep 8, 2023
272642d
Merge branch 'main' into address-field-add-super-scaffold-and-document
pascallaliberte Sep 8, 2023
55b0a04
scaffold strong params for address_field, initialize empty before_action
pascallaliberte Sep 8, 2023
a6c561a
fix set_default_(address) callback
pascallaliberte Sep 11, 2023
c1fffe1
linter
pascallaliberte Sep 11, 2023
3271377
add showcase preview for address_field
pascallaliberte Sep 14, 2023
8681435
docs: start address-field page, add to field-partials
pascallaliberte Sep 15, 2023
bf7a759
fix available field partials table
pascallaliberte Sep 18, 2023
45ce1b7
title case the headings
pascallaliberte Sep 18, 2023
d49d7bc
add Attribute Partials section in showcase (under Field Partials)
pascallaliberte Sep 18, 2023
6a35c51
add showcase preview for atttribute_partials/address
pascallaliberte Sep 18, 2023
2e083d3
document customizing address output
pascallaliberte Sep 18, 2023
eb00be4
showcase: pare down options for address_field
pascallaliberte Sep 18, 2023
afa5dc8
address_field: remove Address.new from sample code
pascallaliberte Sep 18, 2023
8088c86
add _dependent_fields_turbo_frame partial
pascallaliberte Sep 25, 2023
ad566f6
address_field: use dependent_fields_turbo_frame
pascallaliberte Sep 25, 2023
994424b
dependent_fields_turbo_frame: yield stimulus_controller name
pascallaliberte Sep 25, 2023
0fc1f13
address_field: dependent_fields_controller_name
pascallaliberte Sep 25, 2023
8eba7c9
address_field: use form.field_id for id of turbo_frame
pascallaliberte Sep 25, 2023
b5b9ee6
docs/field-partials: add address_field link at the bottom
pascallaliberte Sep 25, 2023
a13c1a2
rename refresh-fields to dependent-fields-frame
pascallaliberte Sep 25, 2023
28a48d6
docs: add dynamic-forms-dependent-fields
pascallaliberte Sep 25, 2023
a661e58
add showcase preview for _dependent_fields_frame
pascallaliberte Sep 26, 2023
5b6e319
about_attribute_partials: compress to partial.body.optional.yield
pascallaliberte Sep 28, 2023
a4cb3cf
about_attribute_partials: compress another partial.options.optional.y…
pascallaliberte Sep 28, 2023
956b106
docs: add missing `do` on render
pascallaliberte Sep 28, 2023
ca07a74
dependent_fields_frame showcase: clarify code example comment
pascallaliberte Sep 28, 2023
7f0027b
super-select doc: add link to dynamic forms doc
pascallaliberte Sep 28, 2023
ec18466
buttons doc: add link to dynamic forms doc
pascallaliberte Sep 28, 2023
9f5667d
Merge branch 'main' into address-field-add-super-scaffold-and-document
pascallaliberte Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module RefreshFieldsHelper
module DependentFieldsFrameHelper
def accept_query_string_override_for(form, method)
field_name = form.field_name(method)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default class extends Controller {
}
static classes = [ "loading" ]

updateFrameFromDependentField(event) {
updateFrameFromDependableField(event) {
pascallaliberte marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
6 changes: 3 additions & 3 deletions bullet_train-fields/app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import PasswordController from './fields/password_controller'
import PhoneController from './fields/phone_controller'
import SuperSelectController from './fields/super_select_controller'
import DependableController from './dependable_controller'
import RefreshFieldsController from './refresh_fields_controller'
import DependentFieldsFrameController from './dependent_fields_frame_controller'

export const controllerDefinitions = [
[FieldController, 'fields/field_controller.js'],
Expand All @@ -27,7 +27,7 @@ export const controllerDefinitions = [
[PhoneController, 'fields/phone_controller.js'],
[SuperSelectController, 'fields/super_select_controller.js'],
[DependableController, 'dependable_controller.js'],
[RefreshFieldsController, 'refresh_fields_controller.js'],
[DependentFieldsFrameController, 'dependent_fields_frame_controller.js'],
].map(function(d) {
const key = d[1]
const controller = d[0]
Expand All @@ -50,5 +50,5 @@ export {
PhoneController,
SuperSelectController,
DependableController,
RefreshFieldsController,
DependentFieldsFrameController,
}
1 change: 1 addition & 0 deletions bullet_train-super_scaffolding/lib/scaffolding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def self.mysql?

def self.valid_attribute_type?(type)
[
"address_field",
"boolean",
"buttons",
# TODO: We're leaving cloudinary_image here for now for backwards compatibility.
Expand Down
2 changes: 2 additions & 0 deletions bullet_train-super_scaffolding/lib/scaffolding/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ def partial_name
"text"
when "number_field"
"number"
when "address_field"
"address"
else
raise "Invalid field type: #{type}."
end
Expand Down
44 changes: 43 additions & 1 deletion bullet_train-super_scaffolding/lib/scaffolding/transformer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -870,8 +870,11 @@ def valid_#{attribute.collection_name}
<td#{cell_attributes}><%= render 'shared/attributes/#{attribute.partial_name}', attribute: :#{attribute.is_vanilla? ? attribute.name : attribute.name_without_id_suffix}#{", #{table_cell_options.join(", ")}" if table_cell_options.any?} %></td>
ERB

if attribute.type == "password_field"
case attribute.type
when "password_field"
field_content.gsub!(/\s%>/, ", options: { password: true } %>")
when "address_field"
field_content.gsub!(/\s%>/, ", one_line: true %>")
end

unless ["Team", "User"].include?(child)
Expand Down Expand Up @@ -958,6 +961,20 @@ def valid_#{attribute.collection_name}
if attribute.type == "file_field"
scaffold_add_line_to_file(file, "#{attribute.name}_removal: [],", RUBY_NEW_ARRAYS_HOOK, prepend: true)
end
elsif attribute.type == "address_field"
address_strong_params = <<~RUBY
#{attribute.name}_attributes: [
:id,
:_destroy,
:address_one,
:address_two,
:city,
:country_id,
:region_id,
:postal_code
],
RUBY
scaffold_add_line_to_file(file, address_strong_params, RUBY_NEW_ARRAYS_HOOK, prepend: true)
else
scaffold_add_line_to_file(file, ":#{attribute.name},", RUBY_NEW_FIELDS_HOOK, prepend: true)
if attribute.type == "file_field"
Expand All @@ -969,6 +986,28 @@ def valid_#{attribute.collection_name}
scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", attribute.special_processing, RUBY_NEW_FIELDS_PROCESSING_HOOK, prepend: true) if attribute.special_processing
end

#
# ASSOCIATED MODELS
#

unless cli_options["skip-form"] || attribute.options[:readonly]

# set default values for associated models.
case attribute.type
when "address_field"
scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", "before_action :set_default_#{attribute.name}, except: :index", "ApplicationController", increase_indent: true)

method_content = <<~RUBY

def set_default_#{attribute.name}
@tangible_thing.#{attribute.name} ||= Address.new
end
RUBY
scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", method_content, "end", prepend: true, increase_indent: true, exact_match: true)
end

end

#
# API SERIALIZER
#
Expand Down Expand Up @@ -1246,6 +1285,9 @@ def remove_#{attribute.name}
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "after_validation :remove_#{attribute.name}, if: :#{attribute.name}_removal?", CALLBACKS_HOOK, prepend: true)
when "trix_editor"
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "has_rich_text :#{attribute.name}", HAS_ONE_HOOK, prepend: true)
when "address_field"
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "has_one :#{attribute.name}, class_name: \"Address\", as: :addressable", HAS_ONE_HOOK, prepend: true)
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "accepts_nested_attributes_for :#{attribute.name}", HAS_ONE_HOOK, prepend: true)
when "buttons"
if attribute.is_boolean?
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "validates :#{attribute.name}, inclusion: [true, false]", VALIDATIONS_HOOK, prepend: true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<% showcase.description do %>
When you're building forms, use Bullet Train's Field Partials for your form fields. They DRY-up all the presentation logic without needing a third-party dependency like Formtastic.
<br/>
<br/>
Read much more about them in the extensive <a href="https://bullettrain.co/docs/field-partials" target="_blank">developer documentation</a>.
<% end %>

<% form_with model: Scaffolding::CompletelyConcrete::TangibleThing.new, url: "#" do |form| %>
<%
form.object.address_value = Address.new
%>

<% showcase.sample "Basic" do %>
<%= render 'shared/fields/address_field', form: form, method: :address_value %>
<% end %>
<% end %>

<%# To display further options use `showcase.options.x` as options with a block will clear the old options. See `_options.html.erb` for an example. %>
<% showcase.options do |o| %>
<% o.required :form, "Reference to the form object", type: "ActionView::Helpers::FormBuilder" %>
<% o.required :method, "Attribute of the model" %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<% showcase.description do %>
Follows the <a href="/docs/field-partials/dynamic-forms-dependent-fields.md" target="_blank">Dependent Fields Pattern (See developer documentation)</a> to display form fields that depend on the value of another field.
<% end %>

<% form_with model: Scaffolding::CompletelyConcrete::TangibleThing.new, url: "#" do |form| %>
<%
form.object.boolean_button_value = false
%>

<% showcase.sample "Basic" do %>
<%# here we're wrapping the field to trap the `change` event %>
<%= tag.div data: {
'controller': "dependable",
'action': 'change->dependable#updateDependents',
'dependable-dependents-selector-value': "##{form.field_id(:button, :dependent_fields)}"
} do %>
<%= render "shared/fields/buttons",
form: form,
method: :boolean_button_value,
other_options: { label: "Should I present more fields?" } %>
<% end %>

<%= render "shared/fields/dependent_fields_frame",
id: form.field_id(:button, :dependent_fields),
form: form,
dependable_fields: [:boolean_button_value] do %>

<div class="my-3">
<% if form.object.boolean_button_value %>
<strong>More fields would be shown here.</strong>
<% else %>
<em>No fields should be shown here.</em>
<% end %>
</div>

<% end %>
<% end %>
<% end %>

<%# To display further options use `showcase.options.x` as options with a block will clear the old options. See `_options.html.erb` for an example. %>
<% showcase.options do |o| %>
<% o.required :id, "id of the turbo_frame element" %>
<% o.required :form, "Reference to the form object", type: "ActionView::Helpers::FormBuilder" %>
<% o.required :dependable_fields, "Attributes of the model for the fields on whose values this frame depends", type: "Array" %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<% showcase.description do %>
Attribute partials are used to display a single attribute of a model, within an `index` table colum or the resource's `show` screen.

Read more about them and about the form field partials in the extensive <a href="https://bullettrain.co/docs/field-partials" target="_blank">developer documentation</a>.
<% end %>

<%= partial.body.optional.yield Scaffolding::CompletelyConcrete::TangibleThing.new %>


<%# To display further options use `showcase.options.x` as options with a block will clear the old options. See `_options.html.erb` for an example. %>
<% showcase.options do |o| %>
<% o.required :object, "Reference to the model object", type: "Object" %>
<% o.required :attribute, "Attribute of the model" %>
<%= partial.options.optional.yield o %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<%= render "showcase/previews/field_partials/attribute_partials/about_attribute_partials", showcase: showcase do |partial| %>
<% partial.body do |object| %>
<% object.address_value = Address.new(
country_id: 233,
address_one: "2800 East Observatory Road",
city: "Los Angeles",
region_id: 1416,
postal_code: "90027"
)
%>

<% showcase.sample "Multi-line" do %>
<%= render 'shared/attributes/address', object: object, attribute: :address_value %>
<% end %>

<% showcase.sample "One-line" do %>
<%= render 'shared/attributes/address', object: object, attribute: :address_value, one_line: true %>
<% end %>
<% end %>

<% partial.options do |o| %>
<% o.optional :one_line, "Render into a single line", type: "Boolean" %>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@
form ||= current_fields_form
options ||= {}
other_options ||= {}
id_of_dependent_fields_frame = form.field_id(method, :dependent_fields)
%>

<%= form.fields_for(method) do |address_form| %>
<% with_field_settings form: address_form do %>

<% # For self-updating the fields in the turbo_frame further down %>
<% accept_query_string_override_for(address_form, :country_id) %>

<%= render 'shared/fields/super_select',
method: :country_id,
choices: populate_country_options,
Expand All @@ -23,7 +19,7 @@ id_of_dependent_fields_frame = form.field_id(method, :dependent_fields)
data: {
'controller': "dependable",
'action': '$change->dependable#updateDependents',
'dependable-dependents-selector-value': "##{id_of_dependent_fields_frame}"
'dependable-dependents-selector-value': "##{form.field_id(:country, :dependent_fields)}"
}
}
%>
Expand All @@ -38,13 +34,11 @@ id_of_dependent_fields_frame = form.field_id(method, :dependent_fields)
%>
</div>
<div class="sm:col-span-2">
<%= turbo_frame_tag id_of_dependent_fields_frame,
class: "block space-y-5",
data: {
'controller': "refresh-fields",
'action': "dependable:updated->refresh-fields#updateFrameFromDependentField turbo:frame-render->refresh-fields#finishFrameUpdate",
'refresh-fields-loading-class': 'opacity-60'
} do
<%= render "shared/fields/dependent_fields_frame",
id: form.field_id(:country, :dependent_fields),
form: address_form,
dependable_fields: [:country_id],
html_options: { class: "block space-y-5" } do |dependent_fields_controller_name|
%>

<div class="grid grid-cols-1 gap-y gap-x sm:grid-cols-2">
Expand All @@ -61,7 +55,7 @@ id_of_dependent_fields_frame = form.field_id(method, :dependent_fields)
html_options: {
disabled: address_form.object.country_id.nil?,
data: {
"refresh-fields-target": "field"
"#{dependent_fields_controller_name}-target": "field"
}
}
%>
Expand All @@ -71,7 +65,7 @@ id_of_dependent_fields_frame = form.field_id(method, :dependent_fields)
<%= render 'shared/fields/text_field', method: :postal_code,
options: {
data: {
"refresh-fields-target": "field"
"#{dependent_fields_controller_name}-target": "field"
}
},
other_options: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<%
stimulus_controller = "dependent-fields-frame"
html_options ||= {}
html_options[:data] ||= {}
html_options[:data][:controller] ||= ""
html_options[:data][:controller] += " #{stimulus_controller}"
html_options[:data][:action] ||= ""
html_options[:data][:action] += " dependable:updated->#{stimulus_controller}#updateFrameFromDependableField turbo:frame-render->#{stimulus_controller}#finishFrameUpdate"
html_options[:data]["#{stimulus_controller}-loading-class"] ||= "opacity-60"

dependable_fields ||= []
%>
<%= turbo_frame_tag id, **html_options do %>
<%
dependable_fields.each do |method|
accept_query_string_override_for(form, method)
end
%>

<%= yield stimulus_controller %>
<% end %>