改良中...心に優しいエラー表示を!
現状で、もしすべての行で検証エラーが発生したとすると、こんな感じになる...。
ずぶん派手に教えてくれる。何処がどんなエラーなのか明快と言えば明快なのだが...この表示、何度も見てると、なんだか気持ちを逆撫でされているようで、いつのまにか「言われなくても分かっとるわぃ!」とムキになってくるのだ。作っている本人がそうなのだから、ユーザーがこのエラーを見たら、おそらく、やる気が失せるだろう...。もう少し、心に優しいエラー表示が必要だ。
配色
赤過ぎると挑発的な感じになってしまう。もう少しトーンを抑えた色にしてみる。
- エラー時の背景色をピンクに変更した。
- paddingを0にした。
- 控え目になるので、input、textarea、selectの背景色もピンクにしてみた。
/* スタイルシート: public/stylesheets/scaffold.css */ ...(中略)... margin: 0; padding: 0; background-color: pink; display: table; } .fieldWithErrors * { background: pink; } ...(中略)...
配置
エラーが発生すると、大した情報量でもないのに、スクロールしないとページを見渡せなくなってしまう。表示をコンパクトにまとめる必要がある。
- エラーの看板リスト表示方式をやめて、個々のフィールドの直下にエラーメッセージを表示するようにしてみる。
- error_message_onを利用して、伝票、明細行の_form.rhtmlを以下のように変更してみた。
- add_to_baseを使ったフィールドを指定しない検証*1は、<%= error_message_on 'slip', 'base' %>で表示することができる。(<table class="journal">の直前に追加した。)
<%# ビュー: app/views/slips/_form.rhtml %> <%#= error_messages_for 'slip' %> <!--[form:slip]--> <p><label for="slip_number">伝票No.</label><br /> <%= text_field 'slip', 'number', :autocomplete=>'off' %> <%= error_message_on 'slip', 'number' %> </p> <p><label for="slip_executed_on">実行日</label><br /> <%= text_field 'slip', 'executed_on', :autocomplete=>'off' %> <%= error_message_on 'slip', 'executed_on' %> </p> <p><label for="slip_total_yen">合計金額</label><br /> <%= yen_field 'slip', 'total_yen' %> <%= error_message_on 'slip', 'total_yen' %> </p> <%= error_message_on 'slip', 'base' %> <table class="journal"> <%= render :partial=>'journals/header' %> <%= render :partial=>'journals/form', :collection=>@journals %> </table> <!--[eoform:slip]-->
<%# ビュー: app/views/slips/_form.rhtml %> <% @journal = form %> <% @journal_count += 1 rescue @journal_count = 1 %> <!-- 削除 <tr> <td> </td> <td colspan="2"> <%#= error_messages_for 'journal' %> </td> </tr> ここまで削除 --> <!--[form:journal]--> <tr valign="top"> <th align="right"> <%= @journal_count %> <%= hidden_field "journal", 'position', :index=>@journal.index, :value=>@journal_count %> </th> <td align="_center"> <%= text_field "journal", 'comment', :index=>@journal.index, :size=>40 %> <%= error_message_on 'journal', 'comment' %> </td> <td align="_center"> <%= yen_field 'journal', 'yen', :index=>@journal.index %> <%= error_message_on 'journal', 'yen' %> </td> </tr> <!--[eoform:journal]-->
- スタイルシートも以下のように追記した。
/* スタイルシート: public/stylesheets/scaffold.css */ .formError { font-size: x-small; color: red; }
問題
しかし、ここで問題発生!気になることは以下の3点。
- フィールドで複数のエラーが発生しているはずなのに、一つだけしかエラー表示されていない。
- エラーメッセージの主語が省略されてしまう。主語があることを前提としたメッセージなので「が合計金額と一致していません。」のようなおかしな表現になっている。
- エラーが発生すると、伝票No.・実行日・合計金額と、入力フィールドの間に不要なスペース行が入ってしまう。なぜ?
問題1: error_messages_onが必要!
Rails謹製のメソッドは以下の二つ。
error_messages_for error_message_on
個々のフィールド直下にエラーメッセージを表示する為にerror_message_onを利用したが、どうやらこのメソッドは、指定したフィールドで発生しているエラーの最初の一つだけしか表示してくれない仕様のようだ。*2よくよく見ると、error_message(単数形)_onとなっており、メソッド名的には筋が通っているのか...。
それならここでは、フィールドごとにすべてのエラーを表示してくれるerror_messages_onが必要だ。error_message(単数形)_onのソースを参考にヘルパーに以下のように書いてみた。
# ヘルパー: app/helpers/application_helper.rb module ApplicationHelper ...(中略)... def error_messages_on(object, method, prepend_text = "", append_text = "", css_class = "formError") if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && (errors = obj.errors.on(method)) errors_list = errors.map {|error| "#{prepend_text}#{error}#{append_text}<br />"}.join content_tag("span", errors_list, :class => css_class) else '' end end end
すべてのerror_message(単数形)_onを、新しく作ったヘルパメソッドerror_messages_onに変更して確認すると...エラーメッセージが複数表示されるようになった!
問題2: Ruby-GetTextを利用する
エラーメッセージの主語が省略されるのもerror_message_on(またはerror_messages_on)の仕様だが、オプションとしてprepend_textを設定できるようになっている。ここに主語にあたる文字列を設定しておけば良いのだ。が...個々のerror_messages_onでフィールド名を設定するのは面倒だ。デフォルトでerror_messages_forのように主語ありのエラーメッセージになれば良いのに...。
この問題はRuby-GetTextを利用することで解決する。既にインストールしてあれば、お決まりのコード2行と、オリジナルなvalidateのエラーメッセージには%{fn}を追記するだけでOK。
# 設定: config/environment.rb
...(中略)...
# Include your application configuration below
require 'gettext/rails'
# コントローラー: app/controllers/application_controller.rb class ApplicationController < ActionController::Base ...(中略)... init_gettext 'test_slip' end
- environment.rbを変更したら、サーバーの再起動を忘れずに。
- validateでオリジナルなエラーメッセージを設定している場合は、文字列中に%{fn}と書くと、Ruby-GetTextがそれをフィールド名に置き換えてくれる。(オレンジ色の箇所)
# モデル: app/models/slip.rb class Slip < ActiveRecord::Base has_many :journals, :order=>:position, :dependent=>:destroy validates_presence_of :number, :executed_on, :total_yen def validate # 明細の入力チェック errors.add_to_base("明細が一行も入力されていません。") unless journals_valid? # 合計金額のチェック # nilが含まれると数値として取り扱えないので、to_iで数値に変換しておく必要あり errors.add(:total_yen, "%{fn}が明細の合計と一致していません。") unless total_yen.to_i == journals_total_yen end ...(中略)...
class Journal < ActiveRecord::Base belongs_to :slip validates_presence_of :comment, :yen validates_exclusion_of :yen, :in=>(0..0), :message=>"%{fn}0 は入力できません。" def validate errors.add(:yen, "%{fn}が合計金額と一致していません。") unless slip.total_yen.to_i == slip.journals_total_yen end ...(中略)...
- ついでに、フィールド名も翻訳しておいた。
-
-
- Ruby-GetTextのちょっとした使い方については以前の日記:GetTextで日本語化してみる辺りが多少は参考になるかもしれません。(詳細はRuby-GetText本家のページへ)
-
これでエラーメッセージも自然な日本語になった!
問題3: fieldWithErrorsのdivタグをspanタグに変更する
些細な空行のことだが、結構悩んでしまった...。(scaffoldが生成する入力フォームではすべて、検証エラーが発生するとラベルとフォームの間に余分な空行が入ってしまう...。)
- scaffoldは以下のような形式の入力フォームを生成してくれる。
<!--[form:slip]--> <p><label for="slip_number">Number</label><br /> <%= text_field 'slip', 'number' %> </p> ...(中略)...
- 上記は検証エラーが無ければ、以下のhtmlに変換される。
<!--[form:slip]--> <p><label for="slip_number">Number</label><br /> <input id="slip_number" name="slip[number]" size="30" type="text" /> </p>
- もし検証エラーが発生すれば、以下のhtmlが生成される。(<div class="fieldWithErrors">タグで囲まれる。)
<!--[form:slip]--> <p><label for="slip_number">伝票No.</label><br /> <div class="fieldWithErrors"><input id="slip_number" name="slip[number]" size="30" type="text" /></div> </p>
- 頭で考えると、これの何処に空行が入る要因があるのか、全く理解できなかった...。(もしかして<br />タグが怪しいと思ったりして、削除してみたが、エラー無しの状態ではラベルとフォームが改行無しの一行で表示され、エラーが発生するとやはり同じように空行が入った状態になってしまう。)
- ところが、Firebugでinspectしてみて気付いた!pタグの中にあるはずのdivタグが、pタグの外に弾き出されてしまっている...。以下のような状況になっている。
<p> <label for="slip_number">伝票No.</label><br/> </p> <div class="fieldWithErrors"> <input id="slip_number" type="text" size="30" name="slip[number]" autocomplete="off"/> </div>
- これはtableタグの直下にdivタグが存在する場合、同じようにtableタグの外に出されて表示される現象と同じではないか?と考えた。pタグの中にdivタグの存在は許されないのかもしれない。*3
- そこで、検証エラーが発生したとき<span class="fieldWithErrors">タグで囲うように変更してみた。*4environment.rbで以下のように設定することで、すべてのfieldWithErrorsのタグがspanタグに変更される。
-
-
- rails:285 あべさんのスレッドに感謝です!
-
# 設定: config/environment.rb ...(中略)... # Include your application configuration below # fieldWithErrorsのタグを<span>に変更する。 ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| "<span class=\"fieldWithErrors\">#{html_tag}</span>" end
- environment.rbを変更したら、サーバーの再起動を忘れずに。
-
- なぜ上記コードでspanタグに置き換えられるのか、自分では理解できていない...。そもそも、検証エラー発生時にどのような仕組みでfieldWithErrorsクラスのタグが挿入されるのか、それさえも理解できていない。Railsのソースのどの部分を読むべきなのか?知りたい...。
-
以上で3つの問題が解決できた!サーバーを再起動して試してみる。
心に優しいエラー表示になっただろうか?
*1:例:errors.add_to_base("明細が一行も入力されていません。") unless journals_input?
*2:マニュアルのshow sourceで確認すると、コードの中に「errors.is_a?(Array) ? errors.first : errors」となっている箇所がある。それにしても、どうしてこんな仕様になっているんだろう?
*3:htmlで正しくは、divは複数の段落pなどブロックレベル要素をグループ化するために使うようだ。
*4:spanは段落内の特定の文字列などインラインレベル要素をグループ化するために使うようだ。