-
-
Save sergii/97c05eaa73a6fdf6a75031eea5af0e65 to your computer and use it in GitHub Desktop.
Render inline errors in Rails forms after submit and hotwire changes
This file contains hidden or 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
<%= form_for customer, html: { id: dom_id(customer), class: 'row g-3' } do |f| %> | |
<% presenter = InlineErrorRenderer.new(customer, self, 'form-control-lg form-control-solid mb-3 mb-lg-0') %> | |
<div class='col-md-6'> | |
<%= f.label :full_name %> | |
<%= f.text_field :full_name, presenter.html_options_for(:full_name) %> | |
<%= presenter.error_container_for(:full_name) %> | |
</div> | |
<div class='col-md-6'> | |
<%= f.label :phone_number, 'Phone Number' %> | |
<%= f.text_field :phone_number, presenter.html_options_for(:phone_number) %> | |
<%= presenter.error_container_for(:phone_number) %> | |
</div> | |
<div class='col-md-6'> | |
<%= f.label :email, 'Email' %> | |
<%= f.text_field :email, presenter.html_options_for(:email) %> | |
<%= presenter.error_container_for(:email) %> | |
</div> | |
<div class="col-md-6"> | |
<%= f.label :country, 'Country' %> | |
<%= country_select(f, "country", { priority_countries: ['IN', 'US'], selected: "IN" }, presenter.html_options_for(:country)) %> | |
<%= presenter.error_container_for(:country) %> | |
</div> | |
<div class="col-12"> | |
<%= f.submit 'Submit', class: 'btn btn-outline-secondary' %> | |
<%= link_to 'Back', customers_path, class: 'btn btn-outline-warning' %> | |
</div> | |
<% end %> |
This file contains hidden or 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
// Rails forms when in error state wraps each form element inside div with "field_with_errors" class. | |
// Therefore bootstrap error classes (invalid-feedback, is-invalid) won't work because of following structure. | |
// <div class="form-control"> | |
// <div class="field_with_errors"> | |
// <input type="text" class="is-invalid" | |
// </div> | |
// </div> | |
.invalid-feedback { | |
display: block !important; | |
} | |
.form-control.form-control-solid.is-invalid { | |
border-color: #F1416C; | |
} |
This file contains hidden or 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 is optional but if you want to use hotwire then just return the error response in turbo_stream in controller | |
def create | |
@customer.user_id = current_user.id | |
respond_to do |format| | |
if @customer.save | |
# do usual stuff | |
else | |
format.turbo_stream | |
end | |
end | |
end | |
# and handle that turbo_stream response in create.turbo_stream.erb | |
<%= turbo_stream.replace "new_customer", partial: "customers/form", locals: { customer: @customer } %> |
This file contains hidden or 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
# InlineErrorRenderer is based on TwitterBootstrap's server side form validations. | |
# https://getbootstrap.com/docs/5.0/forms/validation/#server-side | |
class InlineErrorRenderer | |
def initialize(model, template, default_classes) | |
@model = model | |
@template = template | |
@default_classes = default_classes | |
end | |
FORM_INVALID = '%s %s is-invalid' | |
ARIA = '%s_aria' | |
def html_options_for(attr, html_class = nil) | |
if errors[attr].present? | |
{ | |
class: format(FORM_INVALID, default_classes, html_class), | |
aria: { describedby: format(ARIA, attr) } | |
} | |
else | |
{ class: default_classes } | |
end | |
end | |
def error_container_for(attr) | |
if errors[attr].present? | |
template.tag.div(class: 'invalid-feedback', id: format(ARIA, attr)) do | |
errors[attr].join(',').html_safe | |
end | |
end | |
end | |
private | |
attr_reader :model, :template, :default_classes | |
def errors | |
@errors ||= model.errors | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment