【Rails5】オートコンプリート付きのタグ入力 〜バッジ風〜

2019年5月18日

記事や写真などにタグを付けるというのは昨今よく見るシーンです。この記事ではタグの入力フォームに自動予測機能を加えてタグ入力時にバッジ風な見せ方にする方法をご紹介します。

自分で書いていて全くピンと来ない説明だったので完成図を添えておきます。

これ↓を作ろうと思います:

1.準備

1-1. jQuery pluginのダウンロード

以下のページからプラグインをダウンロードします。

  1. jQuery autocomplete
  2. jQuery-Tags-Input

jQuery autocompleteをダウンロードする際に必要な部分のみをダウンロードするにはダウンロードビルダー ページのComponents  > Toggle allのチェックを外し

Widgets > Autocompleteにチェックを入れると自動的に必要なものだけにチェックが入ります。

ページ最下部にある「Download」ボタンを押し、プラグインをダウンロードしてください。

同様に、jQuery-Tags-Inputページの「Download ZIP」ボタンを押して、プラグインをダウンロードしてください。

ZIPファイルをダウンロードし終えたら、vendorディレクトリにassetsディレクトリを作成し、その中に

  1. css
  2. javascripts

ディレクトリを作り、minifiedされたcssファイルとjavascriptファイルを入れてください。

アプリケーションがちゃんとファイルを読み込むようにapplication.jsとapplication.css(もしくはapplication.scss)に適切な処理を書き加えます。

1-2. Modelの作成と設定

今回は記事にタグを付ける形にしようと思うのでPost Modelをscaffoldします。

そしてタグはhas_many through でPostモデルに関連付けるのでTag ModelとTagging Modelを作ります。

各種モデルを生成したら一旦db:migrateしましょう

それではそれぞれの関係性などを設定していきましょう。

Taggingモデルは何も追記する必要はありません。生成した時にreferencesをつけたので以下のようになっていると思います。

タグは記事と一緒に生成できるようにPost.rbにaccepts_nested_attributes_forをつけています。空のタグが生成されるのを回避するためにreject_ifでnameプロパティが空の場合はrejectするように書いています。

2.フォームの用意

2-1. 記事投稿と一緒にタグの生成

まずは記事を投稿するフォームと一緒にタグ付けようの入力ボックスを作ります。

ヘッダーのコードなどはBootstrap Example から一部拝借しています。

この状態で一度コンソールからrails sをして、localhost:3000/posts/newにアクセスしてみましょう。

タグの入力フォームが表示されていません。タグの入力フォームを表示させるにはTagモデルのオブジェクトをbuildしないといけません。

もう一度localhost:3000/posts/newにアクセスしてみましょう。

今度は表示されました。ですがまだ終わりではありません。この状態でタイトルとタグを入力して投稿しても、タグだけ弾かれます。Tagも一緒に生成するにはPostのStrong Parameterにtags_attributeを追加しないといけません。

これでPostと一緒にTagも生成されるようになりました。PostControllerのcreate actionとindex actionを少し修正して、記事を投稿してみましょう。

タグも一緒に生成されていますね。

2-2. 既存のタグの確認と関連付け

無事に生成されたのは良いのですが、ここでまた一つ不都合なことがあります。次に新しく記事を投稿する際にタグ入力フォームに「タグ①」と入力して記事を投稿すると、先ほど生成したタグとは別に二つ目の「タグ①」が生成されます。

同じタグ名でも毎回新しいタグが生成されるのは無駄ですし、特定のタグに関連している記事数を出したい場面もあるかもしれません。

理想としては、記事が保存される前に既存のタグがあるかどうかを判定し、ある場合は既存のタグを関連付けし、無い場合は新しくタグを生成することです。

Postモデルに以下を追加しましょう。

before_save コールバックを使い、Postモデルが保存される前に、タグの存在の有無を判定しています。

tag.name.stripはスペースを排除することによって、同じタグなのに空白によって別のタグとして新しく生成されるのを防ぐためです。

例)
「タグ①」
「タグ① 」
↑は同じタグなのに、スペースがあると別のタグとして生成されてしまう

それではちゃんと同じタグが使われているかどうかを確認するために、以下のファイルを少し修正して新しく記事を投稿して見ましょう。タグは前回入力したものと同じにしてください。

「タグ①」に関連しているPostの数が「2」なので、ちゃんと同じタグが使われていますね。本当に新しくタグが生成されていないかを確認したい場合はコンソールで「rails c」をした後に以下を試してください。

Tagのレコードが一つだけ表示されていれば問題ありません。

3. オートコンプリート

3-1. オートコンプリートとバッジ風の表示

ここまで随分時間が掛かりましたがここからが本番です。タグの入力フォームに自動予測をつけて、タグの表示もバッジ風にします。

posts.js(私はcoffeescriptを書くこと断念した人間なのでjsに変えています)に以下を追加してください。

この記事の冒頭で導入したjQuery-Tags-Inputプラグインを使い、タグの入力フォームにtagsInputします。

そうすると、タグの入力フォームに入力を行うと非同期的にautocomplete_urlで指定したURLにPOSTします。ですので、新しくactionを作りましょう。わざわざこれだけのためにtags_controllerを作りたく無いので、posts_controllerに「tagAutocomplete」actionを追加します(ちゃんとルーティングされていればどこに作っても問題ないので各々のご判断で追加してください)。

params[:term]はautocomplete pluginの仕様でフォームに入力したタグが入っています。入力されたタグが既存のタグと部分的にも一致したらJSON形式でタグを返しています。

次に、返ってきたJSONをrenderします。

(#formTagInput_tag)はjQuery-Tags-Inputプラグインが自動生成するinput要素です。このinput要素に自動予測候補をrenderしています。

それでは一度localhost:3000/posts/newにアクセスして見ましょう。

見た目が若干変わってますね。

右クリック > 「検証」をして要素の検証をして見ましょう。

元々のタグの入力フォーム(’#formTagInput’)に「display:none」が追加され、代わりに(#formTagInput_tagsinput) div 要素が追加されその中に(#formTagInput_tag) input要素がありますね。

試しに「タグ①」と入力して見ましょう。

「タ」と入力したあたりから予測候補に「タグ①」と出てきました。Enterを押すと、バッジ風の表示になります。

しかし再度自動生成される#formTagInput_tagになぜか.not_validクラスがついてしまい(空白として認識されてしまうからですかね?)さらにフォーカスが外れてしまうのでonAddTagオプションを使って .not_validクラスを除外しもう一度focusを合わせます。posts.jsに以下を追加してください。

3-2. 複数タグ

一見ここで完成のように思えますが、さらに不都合なことがあります。試しに「タグ①」と「タグ②」を入力して記事投稿して見ます。

そうすると

コンソールのParametersを見てみると、

と出ています。「”,”」で繋がれた一つの文字列としてControllerに渡されています。ですので、記事が保存される前に「”,”」でsplitして別々のタグとして関連付けを行わないといけません。Post.rbを下記のように修正します。

まず、空の配列 tag_arrayを作り、「”,”」でsplitした文字列を配列の中に挿入して行きます。そして一度buildしたtagオブジェクトをdestroyします。これを行わないと連結されたままのタグが生成されてしまいます。

最後に配列をeachで回して、改めてPostに関連付けています。もう一度「タグ①」と「タグ②」を入力して記事投稿して見ましょう。

今度はちゃんと別々のタグとして生成されました!

最終的なコードは以下になります。

おまけ:オートコンプリートの候補リストに関連記事数も表示する

ありえそうなシチュエーションとして、それぞれのタグに何個の記事が関連付けられているか知りたい場面があると思います。

今回はタグ入力時に出てくる自動予測の候補リストに関連記事数も表示します。

以上、タグの入力フォームに自動予測機能を加えてタグ入力時にバッジ風な見せ方にする方法でした。

参考リンク:

https://qiita.com/swamp09/items/6fb852489b4dc3acfbdd

https://medium.com/inview-technical-blog/rails-5-using-find-or-create-by-for-nested-attributes-ff633aee62a1