Railsでtypeahead.jsを使ったオートコンプリート機能(入力補完機能)の実装手順
はじめに
Railsアプリ内の入力フォームでユーザの入力に合わせた候補を表示するオートコンプリート機能をTwitter社が提供しているtypeahead.js
を使って実装したのでその手順をメモしておきます。
やりたいこと
以下のgifのように、ユーザがフォームで入力を行った時に、その入力内容に合わせた候補を自動で表示するというオートコンプリート機能を実装します。以下のようにNameのテキストフィールドではNameの候補を表示し、EmailのテキストフィールドではEmailの候補を表示できるようにします。なお、以下の例では新規ユーザ作成フォームでの名前とE-mailの入力に対してオートコンプリート機能を実装した例になりますが、これ自体は実用上はなんの意味もないです。
このような機能をお手軽に実装するのにtypeahead.js
というライブラリをTwitter社が提供しているので、これを使います。
typeahead.jsの動作概要
以下の図がtypeahead.jsの簡単な動作イメージになります。
上記のような動作を実現するために、以下の作業が必要になります。
- Viewでユーザの入力を検知してtypeaheadを動作させるjavascriptの追加
- ViewからControllerにパラメータを送るために
routes.rb
にルートを追加(routes.rbの修正) - パラメータを元に候補ワードを検索し、Viewに返すメソッドをControllerに追加(Controllerの修正)
- ViewのフォームにHTMLのclass属性とid属性を付与(Viewの修正)
環境
- Rails 4.2.0
- Ruby 2.2.0
- 今回の例として使用しているRailsアプリは
rails generate scaffold User name:string email:string
で作成したアプリです。Userモデル(nameカラム、emailカラムを持つ)とUserコントローラのみを持つアプリです。
typeaheadを使用するための準備
typeahead.jsをダウンロード
まず準備として、以下のサイトからtypeahead.js
をダウンロードします。以下URLにアクセスし、右側にある「Download ZIP」というボタンをクリックしてダウンロードします。
twitter/typeahead.js | GitHub
ダウンロードしたZIPファイルを解凍すると、解凍したフォルダ内にdist
という名前のフォルダがあります。この中のtypeahead.bundle.min.js
というファイルを使用します。他のファイルとの違いは、上記URLサイトにも書いてあるように、typeahead.js
、bloodhound.js
がオートコンプリート実装に必要なjavascript本体ファイルで、それ以外のmin
が付いているものは圧縮版、bundle
が付いているものはtypeahead.js
、bloodhound.js
の両方を含むファイルです。したがって、typeahead.bundle.min.js
もしくはtypeahead.bundle.js
のいずれかがあればオートコンプリート機能を実装できます。
今回はtypeahead.bundle.min.js
を使うので、このファイルを各自のRailsディレクトリのapp/assets/javascripts
にコピーすればOKです。
typeahead用のCSSファイルをダウンロード
typeaheadによるオートコンプリートで表示される候補ワードの見栄えを良くするために、以下のURLからtypeahead用のCSSファイルを取得し、app/assets/stylesheets
に配置しておきます。
LESS / CSS code for using typeahead.js with Bootstrap 3 | GitHub以上でtypeaheadを使用する準備は完了です。
typeaheadを動作させるjavascriptファイル作成
Viewでのユーザの入力を検出して、入力ワードをControllerにajax通信で送信し、Controllerから候補ワードを受け取って表示するjavascriptファイルを作成しておきます。このjavascriptファイルの中でtypeahead
を使用します。ここでは作成するファイルをautocomp.js
という名前にし、以下の内容を記述します。
var baseurl = "/typeahead?term=%QUERY"
var qst1 = { item: 'name' } //'name'はカラム名
var url1 = baseurl + "&" + jQuery.param(qst1)
// jQuery.param(params)はオブジェクトqst1をシリアライズ
// url1 = "/typeahead?term=%QUERY&item=name"
var usernames = new Bloodhound({
datumTokenizer: function(d) {return Bloodhound.tokenizers.whitespace([d.name]); }, //d.nameのnameはカラム名
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: url1, //上記で定義したurl1を指定
wildcard: ‘%QUERY’ // こちらが無いと動作しないとご指摘頂きました。ありがとうございます。
}
});
// 上記で定義したusernamesの初期化
usernames.initialize();
jQuery( document ).ready(function( $ ) {
$('#user_name.typeahead').typeahead({ // #user_nameは後ほどViewファイルのフォーム部分に付与するid属性名
hint: true,
highlight: true,
minLength: 1
},
{
name: 'name', // 'name'はカラム名
displayKey: 'name', // 'name'はカラム名
source: usernames.ttAdapter()
}).on("typeahead:selected typeahead:autocomplete", function(e, datum) {
return $('#user_name.typeahead').val(datum.name);
// #user_nameは後ほどViewファイルのフォーム部分に付与するid属性名
// datum.nameのnameはカラム名
});
});
上記の簡単な説明を以下の載せます。
baseurl = "/typeahead?term=%QUERY"
は、ユーザの入力ワードの送信先となるURLになります。後ほど/typeahead
というURLとユーザの入力ワードを元に候補ワードを返す処理を行うControllerのメソッドを結びつけるルートをroutes.rb
に追記します。qst1 = { item: 'name' }
では、ユーザの入力ワードに対する候補ワードをどのカラムから取得するかを指定しています。すなわち、ここのname
はUserモデルのnameカラムを指します。url1
がViewからController宛のパラメータを含むURLになります。このURLにterm
、item
という名前の2つのパラメータを含めます。term
には%QUERY
という値(入力ワードに該当します)、term
にはname
が格納されます。そして後述しますが、これらの値をController側でparams[:term]
、params[:item]
で受け取って使うことになります。var usernames = new Bloodhound({...
では、usernames
という名前でbloodound
のオブジェクトを生成しています。bloodhound
は、typeahead.jsと共に動作する候補ワード表示のためのエンジンです。正直なところ詳細をよくわかっていません。jQuery( document ).ready(function( $ ) {...
では、ここで指定したHTMLのid属性とclass属性を持つ要素に対してオートコンプリート機能を付与しています。具体的には、typeahead
という名前のclass属性、user_name
という名前のid属性を持つ要素にtypeaheadによるオートコンプリート機能を付与しています。また、typeaheadのオプションもここで指定しています。
続いて上記のautocomp.js
の中で各自の環境に合わせて変更必要な部分を以下にまとめておきます。
- 1行目 ここの
- 3行目 ここの
- 11行目 11行目の最後、
- 15行目
- 20行目 9行目で定義した
- 23行目
- 29行目 ~ 30行目
- 33行目 23行目と同様に
/typeahead
は後述するroutes.rb
に追加するルートのパスと合わせる必要があります。
name
はUserモデルのカラム名を指します。よって、各自のモデルのカラム名に合わせてください。
d.name
のname
を各自のカラム名に合わせる必要があります。
url1
も5行目で定義した名前に合わせる必要があります。
bloodhound
のオブジェクト名にあわせます。
#user_name.typeahead
は後ほどViewファイルのフォーム部分に付与するHTMLのid属性とclass属性です。
'name'
はカラム名にあわせます。
#user_name.typeahead
は後ほどViewファイルのフォーム部分に付与するHTMLのid属性とclass属性です。
上記のautocomp.js
にemail
のテキストフィールド用にオートコンプリートを行うコードを追加して、autocomp.js
は最終的に以下のようになります。
var baseurl = "/typeahead?term=%QUERY"
var qst1 = { item: 'name' }
var qst2 = { item: 'email' }
var url1 = baseurl + "&" + jQuery.param(qst1)
var url2 = baseurl + "&" + jQuery.param(qst2)
var usernames = new Bloodhound({
datumTokenizer: function(d) {return Bloodhound.tokenizers.whitespace([d.name]); },
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: url1
}
});
var useremails = new Bloodhound({
datumTokenizer: function(d) {return Bloodhound.tokenizers.whitespace([d.email]); },
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: url2
}
});
usernames.initialize();
useremails.initialize();
jQuery( document ).ready(function( $ ) {
$('#user_name.typeahead').typeahead({
hint: true,
highlight: true,
minLength: 1
},
{
name: 'name',
displayKey: 'name',
source: usernames.ttAdapter()
}).on("typeahead:selected typeahead:autocomplete", function(e, datum) {
return $('#user_name.typeahead').val(datum.name);
});
});
jQuery( document ).ready(function( $ ) {
$('#user_email.typeahead').typeahead({
hint: true,
highlight: true,
minLength: 1
},
{
name: 'email',
displayKey: 'email',
source: useremails.ttAdapter()
}).on("typeahead:selected typeahead:autocomplete", function(e, datum) {
return $('#user_email.typeahead').val(datum.email);
});
});
routes.rbの修正
typeaheadはViewのフォームで入力される度にControllerとajax通信を行い、ユーザの入力ワードをControllerのメソッドに送信します。そしてControllerのメソッドはViewから送信されてきた入力ワードを元に候補ワードをViewに返します。この動作のために、Controllerのメソッドへのルートを追加する必要があります。今回はconfig/routes.rb
にusers_controller.rb
内のtypeahead_action
へのルートを追記します。typeahead_action
へのパスとして/typeahead
を指定しています。
get '/typeahead' => 'users#typeahead_action'
この後ここで指定したtypeahead_action
という名前のメソッドをControllerに追加します。
Controllerの修正
routes.rb
に書いたようにtypeahead_action
という名前のメソッドを追加します。そしてこのメソッドの内容は以下のようにします。
def typeahead_action
render json: User.select("#{params[:item]}").where(User.arel_table["#{params[:item]}".to_sym].matches("%#{params[:term]}%")).uniq
end
上記のようにjson形式の値をrender
で返すだけのメソッドです。返す値はUserモデルのActive recordで検索した結果になります。
params[:item]
は候補ワードの取得先であるカラム名であり、params[:term]
はユーザの入力ワードになります。
例として、typeahead_action
が受け取ったparams[:item]
、params[:term]
がそれぞれ「test」、「name」であった場合は以下の検索結果を返すことになります。
User.select("name").where(User.arel_table[:name].matches("%test%")).uniq
これはname
カラムの値の内、「test」を含む値でユニークなものを返します。このname
カラムの検索結果をViewに返し、候補ワードとして表示することになります。上記のように検索カラム名自体もautocomp.js
からのパラメータで指定できるようにすることで、フォームに合わせた候補ワードを返すことができます。よって、params[:item]
にemail
という値が格納されていれば、emailカラムから候補ワードを検索しその結果を返します。
なお、RailsのActive Recordのarel_table
の使い方については以下のサイトが参考になりました。ありがとうございました。
whereにSQLの文字列を渡したくない! | Qiita
Viewの修正
次にtypeaheadによるオートコンプリート機能を実装するフォームを修正します。今回は、新規Userを追加するページであるnew.html.erb
のname
、email
のテキストフィールドにオートコンプリート機能を実装します。すなわち、name
、email
のテキストフィールドのHTMLのclass属性とid属性に、上のautocomp.js
の23行目、33行目で指定した名前の属性を付与します。
テキストフィールドにclass属性、id属性を付与
以下のようにname、emailのテキストフィールドそれぞれにtypeahead
という名前のclass属性、nameのテキストフィールドにuser_name
という名前のid属性、emailのテキストフィールドにuser_email
という名前のid属性を付与します。なお、テキストフィールドにform-control
というclass属性も指定していますが、これはBootstarpのCSSを使用するために指定しているだけであり、typeahead
の動作させるために必要なわけではありません。
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, class: "form-control typeahead", id: "user_name" %>
</div>
<div class="field">
<%= f.label :email %><br>
<%= f.text_field :email, class: "form-control typeahead", id: "user_email" %>
</div>
javascriptを読み込むための記述を追加
new.html.erb
でtypeahead.bundle.min.js
、autocomp.js
を読み込ませるために以下をnew.html.erb
の一番上に追記しておきます。
<%= javascript_include_tag "typeahead.bundle.min" %>
<%= javascript_include_tag "autocomp" %>
最終的にnew.html.erb
は以下のようになります。
<%= javascript_include_tag "typeahead.bundle.min" %>
<%= javascript_include_tag "autocomp" %>
<h1>New User</h1>
<%= form_for(@user) do |f| %>
<% if @user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, class: "form-control typeahead", id: "user_name" %>
</div>
<div class="field">
<%= f.label :email %><br>
<%= f.text_field :email, class: "form-control typeahead", id: "user_email" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= link_to 'Back', users_path %>
以上でViewの修正は完了です。
動作確認
あとは各自のRailsアプリでオートコンプリートを実装したフォームに何か入力してみて候補ワードが表示されればOKです。
思うように動作しない場合の確認方法
ブラウザのデベロッパツールで確認
以下のように、ChromeやFirefox Developer Editionのデベロッパツールを使えば、javascriptでエラーが発生している場合はすぐに分かります。
個人的にはjavascriptが読み込まれていないことが原因である場合が多かったです。特にapp/assets/javascripts/application.js
の中に//= require_tree .
があることが原因だったりします。これについては下記が参考になりました。ありがとうございました。
Rails require_treeを排除し、アセットパイプラインで、コントローラー固有のCSS、JavaScriptを組み込む | Beautiful Ajax
ターミナルで動作確認
Railsをrails s -b 0.0.0.0
で起動した場合は、以下のようにオートコンプリートを実装したフォームで入力を行うと、それと同時にtypeaheadによってControllerに入力ワードがajax通信によって送信され、その送信内容などがターミナルに表示されます。もし入力に応じてターミナルに何も表示されない場合はtypeaheadが動作していない可能性が高いです。
上のgifだと見難いですが、ユーザの入力に応じて以下の様な内容がターミナルに表示されると思います。以下はNameのテキストフィールドに「us」と入力した時の表示内容です。
Started GET "/typeahead?term=us&item=name" for 192.168.33.1 at 2015-03-18 11:35:36 +0900
Processing by UsersController#typeahead_action as JSON
Parameters: {"term"=>"us", "item"=>"name"}
User Load (1.4ms) SELECT DISTINCT "users"."name" FROM "users" WHERE ("users"."name" LIKE '%us%')
Completed 200 OK in 3ms (Views: 1.1ms | ActiveRecord: 1.4ms)
また、ルートが存在しない場合は以下の様にターミナルに表示されるのでわかりやすいです。
Started GET "/typeahead_action?term=te&item=name" for 192.168.33.1 at 2015-03-18 11:46:29 +0900
ActionController::RoutingError (No route matches [GET] "/typeahead_action"):
以上のように、ターミナルの表示内容を見ると動作しない原因がわかるかもしれません。
おわりに
typeahead.jsを使おうとしたら思ったように動作せず時間を取られたので次回のためにメモしました。なお、日本語対応については以下参考サイトが参考になるかもしれません。
参考サイト様
twitter typeaheadはいろいろ改良されていた | Qiita
関連記事
- 公開日:2019/10/04 更新日:2019/10/04
RailsとSendGridでメール送信処理を実装する
Ruby on RailsからAction Mailerを使用してSendGrid経由でメールを送信する処理を実装したのでその手順をまとめます。
- 公開日:2018/05/30 更新日:2018/05/30
ブラウザの操作を自動化できるSeleniumをWSLからRubyで使う
フォーム送信や繰り返し行う必要があるブラウザ操作を自動化できるSeleniumをWindows Subsystem for Linux のUbuntuからRubyを用いて使用するための手順をメモします。
- 公開日:2018/05/27 更新日:2018/05/27
rbenvとruby環境の構築手順
WindowsのWindows Subsystem for LinuxでUbuntuを使いはじめ、その中にrbenvとRuby環境を構築したのでその手順をメモします。なお、UbuntuであればWindows Subsystem for Linux、仮想マシン、純粋なUbuntuのいずれでも同じ手順になります。思っていた以上に簡単に構築できました。
- 公開日:2018/05/06 更新日:2018/05/06
RubyでGoogleスプレッドシートを読み込み・書き込みする
Googleスプレッドシートを自分で作成したRubyコードから読み込んだり、書き込んだりするためのコードについてまとめます。
- 公開日:2016/01/10 更新日:2016/01/10
Railsでwickedpdfを使ってPDF出力する
Ruby on RailsでPDF出力させるのに便利なgemはたくさんありますが、htmlをそのままPDF化してくれる「wickedpdf」が一番お手軽で楽でした。wickedpdfの導入手順と使用方法をメモします。