forms: fix form validation error messages.

* Fix it so that all edit forms show an error banner if the form
  has validation errors. Previously forms had to manually call
  `error_messages_for`, which not all forms did.

* Fix it so that the full validation error message is shown next to each
  input attribute that had errors. Also update the styling of these
  error messages to look better.
This commit is contained in:
evazion
2021-02-22 01:36:54 -06:00
parent b9ea9d2f5a
commit cde76e66f6
24 changed files with 24 additions and 72 deletions

View File

@@ -92,16 +92,6 @@ module ApplicationHelper
DText.strip_dtext(text) DText.strip_dtext(text)
end end
def error_messages_for(instance_name)
instance = instance_variable_get("@#{instance_name}")
if instance&.errors&.any?
%{<div class="error-messages ui-state-error ui-corner-all"><strong>Error</strong>: #{instance.__send__(:errors).full_messages.join(", ")}</div>}.html_safe
else
""
end
end
def time_tag(content, time, **options) def time_tag(content, time, **options)
datetime = time.strftime("%Y-%m-%dT%H:%M%:z") datetime = time.strftime("%Y-%m-%dT%H:%M%:z")
@@ -256,7 +246,14 @@ module ApplicationHelper
def edit_form_for(model, **options, &block) def edit_form_for(model, **options, &block)
options[:html] = { autocomplete: "off", **options[:html].to_h } options[:html] = { autocomplete: "off", **options[:html].to_h }
options[:authenticity_token] = true if options[:remote] == true options[:authenticity_token] = true if options[:remote] == true
simple_form_for(model, **options, &block)
simple_form_for(model, **options) do |form|
if model.try(:errors).try(:any?)
concat tag.div(model.errors.full_messages.join("; "), class: "notice notice-error notice-small")
end
block.call(form)
end
end end
def table_for(...) def table_for(...)

View File

@@ -114,6 +114,7 @@ html {
--form-input-border-color: var(--grey-2); --form-input-border-color: var(--grey-2);
--form-input-placeholder-text-color: var(--grey-4); --form-input-placeholder-text-color: var(--grey-4);
--form-input-validation-error-border-color: var(--red-4); --form-input-validation-error-border-color: var(--red-4);
--form-input-validation-error-text-color: var(--red-5);
--form-button-text-color: var(--text-color); --form-button-text-color: var(--text-color);
--form-button-background: var(--grey-1); --form-button-background: var(--grey-1);
@@ -246,8 +247,6 @@ html {
--keyboard-shortcut-color: var(--inverse-text-color); --keyboard-shortcut-color: var(--inverse-text-color);
--keyboard-shortcut-background-color: var(--grey-6); --keyboard-shortcut-background-color: var(--grey-6);
--error-message-color: var(--red-5);
--login-link-color: var(--red-5); --login-link-color: var(--red-5);
--footer-border-color: var(--grey-1); --footer-border-color: var(--grey-1);
--details-border-color: var(--grey-2); --details-border-color: var(--grey-2);
@@ -326,6 +325,7 @@ body[data-current-user-theme="dark"] {
--form-input-border-color: var(--grey-5); --form-input-border-color: var(--grey-5);
--form-input-placeholder-text-color: var(--grey-3); --form-input-placeholder-text-color: var(--grey-3);
--form-input-validation-error-border-color: var(--red-4); --form-input-validation-error-border-color: var(--red-4);
--form-input-validation-error-text-color: var(--red-5);
--form-button-text-color: var(--grey-9); --form-button-text-color: var(--grey-9);
--form-button-background: var(--grey-2); --form-button-background: var(--grey-2);
@@ -444,8 +444,6 @@ body[data-current-user-theme="dark"] {
--keyboard-shortcut-color: var(--grey-0); --keyboard-shortcut-color: var(--grey-0);
--keyboard-shortcut-background-color: var(--grey-7); --keyboard-shortcut-background-color: var(--grey-7);
--error-message-color: var(--red-4);
--login-link-color: var(--red-4); --login-link-color: var(--red-4);
--footer-border-color: var(--grey-7); --footer-border-color: var(--grey-7);
--details-border-color: var(--grey-7); --details-border-color: var(--grey-7);

View File

@@ -1,9 +1,3 @@
span.error {
display: block;
font-weight: bold;
color: var(--error-color);
}
span.link { span.link {
color: var(--link-color); color: var(--link-color);
cursor: pointer; cursor: pointer;

View File

@@ -1,13 +1,3 @@
div.error-messages {
margin: 1em 0;
padding: 1em;
h1 {
font-size: 1em;
color: var(--error-message-color);
}
}
div#notice { div#notice {
padding: 0.5em; padding: 0.5em;
position: fixed; position: fixed;

View File

@@ -44,8 +44,17 @@ form.simple_form {
} }
} }
&.field_with_errors input { &.field_with_errors {
border: 1px solid var(--form-input-validation-error-border-color); input, select, textarea {
border: 1px solid var(--form-input-validation-error-border-color);
}
span.error {
display: block;
color: var(--form-input-validation-error-text-color);
font-style: italic;
font-size: 0.8em;
}
} }
&.text, &.dtext { &.text, &.dtext {

View File

@@ -2,8 +2,6 @@
<div id="a-new"> <div id="a-new">
<h1>New Artist</h1> <h1>New Artist</h1>
<%= error_messages_for :artist %>
<%= render "form" %> <%= render "form" %>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,4 @@
<%= edit_form_for(ban) do |f| %> <%= edit_form_for(ban) do |f| %>
<%= error_messages_for("ban") %>
<%= f.input :user_name, :as => :string, :input_html => { data: { autocomplete: "user" } } %> <%= f.input :user_name, :as => :string, :input_html => { data: { autocomplete: "user" } } %>
<%= f.input :duration, :hint => "in days" %> <%= f.input :duration, :hint => "in days" %>
<%= f.input :reason %> <%= f.input :reason %>

View File

@@ -3,8 +3,6 @@
<h1>Edit Ban</h1> <h1>Edit Ban</h1>
<%= edit_form_for(@ban) do |f| %> <%= edit_form_for(@ban) do |f| %>
<%= error_messages_for("ban") %>
<%= f.input :duration, :hint => "in days" %> <%= f.input :duration, :hint => "in days" %>
<%= f.input :reason %> <%= f.input :reason %>
<%= f.button :submit, :value => "Ban" %> <%= f.button :submit, :value => "Ban" %>

View File

@@ -1,6 +1,4 @@
<%= edit_form_for(@bulk_update_request) do |f| %> <%= edit_form_for(@bulk_update_request) do |f| %>
<%= error_messages_for("bulk_update_request") %>
<p> <p>
Request aliases or implications using the format shown below. An <b>alias</b> makes the first tag a Request aliases or implications using the format shown below. An <b>alias</b> makes the first tag a
synonym for the second tag. An <b>implication</b> makes the first tag automatically add the second tag. synonym for the second tag. An <b>implication</b> makes the first tag automatically add the second tag.

View File

@@ -1,5 +1,3 @@
<%= error_messages_for :comment %>
<%= edit_form_for(comment, namespace: "post_#{comment&.post_id}_comment_#{comment.id || "new"}", html: { style: ("display: none;" if local_assigns[:hidden]), class: "edit_comment" }) do |f| %> <%= edit_form_for(comment, namespace: "post_#{comment&.post_id}_comment_#{comment.id || "new"}", html: { style: ("display: none;" if local_assigns[:hidden]), class: "edit_comment" }) do |f| %>
<% if comment.new_record? %> <% if comment.new_record? %>
<%= f.hidden_field :post_id %> <%= f.hidden_field :post_id %>

View File

@@ -2,8 +2,6 @@
<div id="a-edit"> <div id="a-edit">
<h1>Edit Favorite Group: <%= @favorite_group.pretty_name %></h1> <h1>Edit Favorite Group: <%= @favorite_group.pretty_name %></h1>
<%= error_messages_for "favorite_group" %>
<%= edit_form_for(@favorite_group) do |f| %> <%= edit_form_for(@favorite_group) do |f| %>
<%= f.input :name, :as => :string, :input_html => { :value => @favorite_group.pretty_name } %> <%= f.input :name, :as => :string, :input_html => { :value => @favorite_group.pretty_name } %>
<%= f.input :post_ids_string, label: "Posts", as: :text %> <%= f.input :post_ids_string, label: "Posts", as: :text %>

View File

@@ -2,8 +2,6 @@
<div id="a-new"> <div id="a-new">
<h1>New Favorite Group</h1> <h1>New Favorite Group</h1>
<%= error_messages_for "favorite_group" %>
<%= edit_form_for(@favorite_group) do |f| %> <%= edit_form_for(@favorite_group) do |f| %>
<%= f.input :name, as: :string, required: true %> <%= f.input :name, as: :string, required: true %>
<%= f.input :post_ids_string, label: "Posts", as: :text %> <%= f.input :post_ids_string, label: "Posts", as: :text %>

View File

@@ -1,5 +1,3 @@
<%= error_messages_for("forum_post") %>
<%= edit_form_for(forum_post, namespace: "forum_post_#{forum_post.id}") do |f| %> <%= edit_form_for(forum_post, namespace: "forum_post_#{forum_post.id}") do |f| %>
<%= f.input :body, as: :dtext %> <%= f.input :body, as: :dtext %>

View File

@@ -1,5 +1,3 @@
<%= error_messages_for("forum_post") %>
<%= edit_form_for(forum_post) do |f| %> <%= edit_form_for(forum_post) do |f| %>
<% if forum_post.topic_id.present? %> <% if forum_post.topic_id.present? %>
<%= f.input :topic_id, :as => :hidden %> <%= f.input :topic_id, :as => :hidden %>

View File

@@ -1,5 +1,3 @@
<%= error_messages_for("forum_topic") %>
<div id="form-content"> <div id="form-content">
<%= edit_form_for(forum_topic) do |f| %> <%= edit_form_for(forum_topic) do |f| %>
<%= f.input :title %> <%= f.input :title %>

View File

@@ -12,8 +12,6 @@
they've verified their email address. they've verified their email address.
<p> <p>
<%= error_messages_for "ip_ban" %>
<%= edit_form_for(@ip_ban) do |f| %> <%= edit_form_for(@ip_ban) do |f| %>
<%= f.input :ip_addr, label: "IP Address", as: :string, hint: "Add /24 to ban a subnet. Example: 1.2.3.4/24" %> <%= f.input :ip_addr, label: "IP Address", as: :string, hint: "Add /24 to ban a subnet. Example: 1.2.3.4/24" %>
<%= f.input :reason, as: :string %> <%= f.input :reason, as: :string %>

View File

@@ -3,8 +3,6 @@
<%= edit_form_for(@pool) do |f| %> <%= edit_form_for(@pool) do |f| %>
<h1>Edit Pool: <%= @pool.pretty_name %></h1> <h1>Edit Pool: <%= @pool.pretty_name %></h1>
<%= error_messages_for "pool" %>
<%= f.input :name, :as => :string, :input_html => { :value => @pool.pretty_name } %> <%= f.input :name, :as => :string, :input_html => { :value => @pool.pretty_name } %>
<%= f.input :description, as: :dtext %> <%= f.input :description, as: :dtext %>
<%= f.input :post_ids_string, as: :text, label: "Posts" %> <%= f.input :post_ids_string, as: :text, label: "Posts" %>

View File

@@ -2,8 +2,6 @@
<div id="a-edit"> <div id="a-edit">
<h1>Edit Saved Search</h1> <h1>Edit Saved Search</h1>
<%= error_messages_for :saved_search %>
<%= edit_form_for(@saved_search) do |f| %> <%= edit_form_for(@saved_search) do |f| %>
<%= f.input :query, :as => :string %> <%= f.input :query, :as => :string %>
<%= f.input :label_string, label: "Labels", hint: "A list of tags to help categorize this search. Space delimited.", input_html: { "data-autocomplete": "saved-search-label" } %> <%= f.input :label_string, label: "Labels", hint: "A list of tags to help categorize this search. Space delimited.", input_html: { "data-autocomplete": "saved-search-label" } %>

View File

@@ -5,8 +5,6 @@
<div id="preview"> <div id="preview">
</div> </div>
<%= error_messages_for "user_feedback" %>
<%= edit_form_for(@user_feedback) do |f| %> <%= edit_form_for(@user_feedback) do |f| %>
<%= f.input :category, :collection => ["positive", "neutral", "negative"], :include_blank => false %> <%= f.input :category, :collection => ["positive", "neutral", "negative"], :include_blank => false %>
<%= f.input :body, as: :dtext %> <%= f.input :body, as: :dtext %>

View File

@@ -11,8 +11,6 @@
<div id="preview"> <div id="preview">
</div> </div>
<%= error_messages_for "user_feedback" %>
<%= edit_form_for(@user_feedback) do |f| %> <%= edit_form_for(@user_feedback) do |f| %>
<%= f.input :user_name, :label => "User", :input_html => { value: @user_feedback.user.try(:name), data: { autocomplete: "user" } } %> <%= f.input :user_name, :label => "User", :input_html => { value: @user_feedback.user.try(:name), data: { autocomplete: "user" } } %>
<%= f.input :category, :collection => ["positive", "neutral", "negative"], :include_blank => false %> <%= f.input :category, :collection => ["positive", "neutral", "negative"], :include_blank => false %>

View File

@@ -6,8 +6,6 @@
be visible on your profile to other Danbooru members, but they won't be visible be visible on your profile to other Danbooru members, but they won't be visible
to search engines.</p> to search engines.</p>
<%= error_messages_for "change_request" %>
<%= edit_form_for(@change_request) do |f| %> <%= edit_form_for(@change_request) do |f| %>
<%= f.input :desired_name, label: "Name" %> <%= f.input :desired_name, label: "Name" %>
<%= f.input :desired_name_confirmation, label: "Confirm name" %> <%= f.input :desired_name_confirmation, label: "Confirm name" %>

View File

@@ -1,6 +1,4 @@
<div id="form-content"> <div id="form-content">
<%= error_messages_for("wiki_page") %>
<%= edit_form_for(@wiki_page, url: wiki_page_path(@wiki_page.id)) do |f| %> <%= edit_form_for(@wiki_page, url: wiki_page_path(@wiki_page.id)) do |f| %>
<%= f.input :title, error: false, input_html: { data: { autocomplete: "tag" } }, hint: "Change to rename this wiki page. Update any wikis linking to this page first." %> <%= f.input :title, error: false, input_html: { data: { autocomplete: "tag" } }, hint: "Change to rename this wiki page. Update any wikis linking to this page first." %>

View File

@@ -9,8 +9,6 @@
</div> </div>
<% end %> <% end %>
<%= error_messages_for("wiki_page") %>
<%= edit_form_for(@wiki_page) do |f| %> <%= edit_form_for(@wiki_page) do |f| %>
<%= f.input :title, error: false, input_html: { data: { autocomplete: "tag" } } %> <%= f.input :title, error: false, input_html: { data: { autocomplete: "tag" } } %>
<%= f.input :other_names_string, as: :string, label: "Other names (#{link_to_wiki "help", "help:translated_tags"})".html_safe, hint: "Names used for this tag on other sites such as Pixiv. Separate with spaces." %> <%= f.input :other_names_string, as: :string, label: "Other names (#{link_to_wiki "help", "help:translated_tags"})".html_safe, hint: "Names used for this tag on other sites such as Pixiv. Separate with spaces." %>

View File

@@ -57,13 +57,13 @@ SimpleForm.setup do |config|
# b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid' # b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :label_input b.use :label_input
b.use :hint, wrap_with: { tag: :span, class: :hint } b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :error, wrap_with: { tag: :span, class: :error } # b.use :error, wrap_with: { tag: :span, class: :error }
## full_messages_for ## full_messages_for
# If you want to display the full error message for the attribute, you can # If you want to display the full error message for the attribute, you can
# use the component :full_error, like: # use the component :full_error, like:
# #
# b.use :full_error, wrap_with: { tag: :span, class: :error } b.use :full_error, wrap_with: { tag: :span, class: :error }
end end
# The default wrapper to be used by the FormBuilder. # The default wrapper to be used by the FormBuilder.
@@ -87,7 +87,7 @@ SimpleForm.setup do |config|
# Method used to tidy up errors. Specify any Rails Array method. # Method used to tidy up errors. Specify any Rails Array method.
# :first lists the first message for each field. # :first lists the first message for each field.
# Use :to_sentence to list all errors for each field. # Use :to_sentence to list all errors for each field.
# config.error_method = :first config.error_method = :to_sentence
# Default tag used for error notification helper. # Default tag used for error notification helper.
config.error_notification_tag = :div config.error_notification_tag = :div