JQuery: Bootstrap popover のcontent htmlの手動セットとcloseボタン設置


Railsで作ったデータベースで、同姓同名のデータがある場合に、その一覧を Bootstrap の popover で表示するようにしたかった(下図のような感じ)。

fig01
姓名のフィールドに入力があった時点で、JQueryのchangeイベントを受けて、検索するアクションをたたいて、getJSONで受け取って表示すればよい。

BootstrapページのpopoverのサンプルはButtonにメッセージを設定して、クリックなどで表示させるものだが、自分で検索の都度にcontentを設定したいし、また、popoverを消すためのcloseボタンも設置したい。どうするか?

まずは、表示したい場所にdivを書く。

<div id="popoverSameNames"></div>

これに対して、popover で content を設定して、show すればよい。

popover.({options}) の中でcontentを指定することもできるが、それだと、再検索したときに表示が切り替わらないことが判明。

こちらにヒントがあった
Bootstrap popover content cannot changed dynamically
書き換えるたびに以下のようにすればよいようだ。

$("#popoverSameNames").data('bs.popover').options.content = samenames_html;

また、closeボタンのつけ方は以下にあった。
How to add a close button to Bootstrap Popover

最終的に以下のようなコードにした。

※getJSONで受け取った同姓同名の情報から samenames_html を作って↓
if (samenames_html.length > 0) {
  $("#popoverSameNames").popover({ 
    title: '同姓同名があります.<span class="close">&times;</span>',
    template: '<div class="popover" role="tooltip" style="width:500px;font-size:80%;"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"><div class="data-content"></div></div></div>',
    html: true
  }).on('shown.bs.popover', function(e){
    var popover = $(this);
    $(this).parent().find('div.popover .close').on('click', function(e){
      popover.popover('hide');
    });
  });
  $("#popoverSameNames").data('bs.popover').options.content = samenames_html;
  $("#popoverSameNames").popover('show');
} else {
  $("#popoverSameNames").data('bs.popover').options.content = "";
  $("#popoverSameNames").popover('hide');
}

これでとりあえずうまく動いている。

以上

Rails+JQuery.validation: Radio Button の errorPlacement


複数ページのフォームでバリデーションを行うケースがあり、railsのモデルのバリデーションが使えないように思ったので、jquery-validation-rails を使うことにした。

しかし、デフォルトのエラー表示の位置が気に入らない。
例えば、下の[     ]が入力フィールドであるとして

姓[     ] 名[     ]

これらを必須項目にすると、姓の入力がない場合、

姓[     ] 姓を入力してください 名[     ]

のように「姓」の後ろにエラーが表示され「名」が離れてしまう。

そこでerrorPlacement を使う。例えば、以下のようにすれば、「姓」も「名」もエラーメッセージを後ろに集めることができる。これは Jquery Validation plug-in custom error placement からの受け売り。

姓<%= f.text_field :family_name %> 名<%= f.text_field :first_name %>
<span id="invalid-registration_family_name"></span> <!-- <=ここでエラー表示 -->
<span id="invalid-registration_first_name"></span>
$("form").validate({
  rules: { ..... 略 ..... },
  messages: { ..... 略 ..... },
  errorPlacement: function(error, element) {
      error.insertAfter('#invalid-' + element.attr('id'));
  }
});

ここからが本題。

ラジオボタンなんかはどうすればいいのだろう。ラジオボタンでは、elementのIDがみんな違っていて、それら全てを span で書かなければならないだろうか?スゴイたくさんの選択肢がある場合に困る。

elementのIDではなくname を使えれば問題ないはずだ。以下のように書き直してみる。

error.insertAfter('#invalid-' + element.attr('id'));
↓
error.insertAfter('#invalid-' + element.attr('name'));

しかし、rails の form_for 要素では element name は例えば name=”registration[xxxx]” のようになっていて、この [ ] 記号がIDとしてはどうもまずいらしい。どうする?

結論↓

いろんなやり方がある。element name を使って、その[]を例えば _ に文字置換するとか。
僕は以下のようにしてみました。正規表現でregistration[]を取り除いてデータのアイテム名だけにしている。
動いてはいるが問題ないだろうか。

以下から選択してください。<span id="invalid-category"></span> <!-- <=ここでエラー表示 -->
<%= f.radio_button :category, '010', {} %><%= f.label :category, "ソフトウェア開発",'010'  %>
<%= f.radio_button :category, '020', {} %><%= f.label :category, "プロバイダ",'020' %>
<%= f.radio_button :category, '030', {} %><%= f.label :category, "製造",'030' %>
$("form").validate({
  rules: { ..... 略 ..... },
  messages: { ..... 略 ..... },
  errorPlacement: function(error, element) {
    var element_name = element.attr('name');
    var field_name = element_name.match(/.+\[(.+)\]/);
    error.insertAfter('#invalid-' + field_name[1]);
  }
});

Rails: 1つの form_for でコンテンツとは別にFile Upload


1つの form_for の中で、添付ファイルをアップロードもやりたいのだけれど、その file_field は別に form_for のコンテンツの項目でもアソシエーションでも何でもないということがあり、以下のように書いたが、うまく行かなかった。

↓View

<%= form_for @note do |f| %>
  <%= f.text_field :title %>
  <%= f.text_field :body %>
  <%= file_field_tag :attachment %>
<% end %>

↓Controller

def create
  @note = Note.new(note_params)
  respond_to do |format|
    if @note.save
      if params[:attachment]
        attachment = params[:attachment] # <= ここでファイル情報を持つオブジェクトが取得できない
        filename = attachment.original_filename # <= したがってoriginal_filenameも取得できない
        ..... 略 ......
      end
      ..... 略 ......
    end
  end
end

def note_params
  params.require(:note).permit(:title, :body)
end

上記で、attachment = params[:attachment] は、ファイル情報を持つオブジェクトが取得できない。ファイル名の文字列だけが取得されている。

attachment を form_for の傘下に含めれば、ファイル情報を持つオブジェクトが取得できる。つまり、以下のようにviewを書けばよいことはわかったが……。

<%= form_for @note do |f| %>
  <%= f.text_field :title %>
  <%= f.text_field :body %>
  <%= f.file_field :attachment %> # <= f. を付けた file_field
<% end %>

そうすると、Strong Parameter も変更か。permitを記述するのはいいことだ……などと考える。

def note_params
  params.require(:note).permit(:title, :body, :attachment)
end

いやいや、これではNoteモデルにない項目を追加することになるし、@note = Note.new(note_params) も当然エラーになってしまう。どうする?

とりあえず、以下のようにしてStrong Paramaterを別にして回避したが、これでいいのだろうか。

↓結論(今のところの)

def create
  @note = Note.new(note_params)
  respond_to do |format|
    if @note.save
      if attachment_params[:attachment]
        attachment = attachment_params[:attachment] # <= ファイル情報を持つオブジェクトが取得できた!
        filename = attachment.original_filename # <= したがってoriginal_filenameも取得OK
        ..... 略 ......
      end
      ..... 略 ......
    end
  end
end

def note_params
  params.require(:note).permit(:title, :body)
end
def attachment_params # <= コレ追加
  params.require(:note).permit(:attachment)
end