🦓

[Rails][Tagify]タグのオートコンプリート

2023/09/01に公開

はじめに

タグの入力フォームにTagifyを使ってオートコンプリートを追加していきます。
タグのCRUD機能はすでに実装されています。

https://github.com/yairEO/tagify

demoサイト:
https://yaireo.github.io/tagify/

環境

Rails 7.0.7
ruby 3.2.1

流れ

  1. Tagの検索機能を作る
  2. Tagifyをインストールする
  3. Tagifyを初期化する
import Tagify from '@yaireo/tagify'
var tagify = new Tagify(...)
  1. タグ用stimulusコントローラーを作成する
  2. テキストエリアにdata-controller="tag"を追加しstimulusと連結させる
  3. タグの検索結果をTagifyに渡し、フロントで表示させる
  4. タグの入力値をRailsに送信する

Tagの検索機能を作る

ユーザーが文字を入力したら、検索クエリを実行しDB内に関連するタグを返して、ユーザーに表示させるようにします。

Tagの検索URLを追加する

config/routes.rb
resources :tags, only: %i[index show] do
  collection do
    get :search
  end
end

searchアクションを作成する

app/controllers/tags_controller.rb
class TagsController < ApplicationController
  def search
    query = params[:query]
    @tags = Tag.where("name LIKE ?", "%#{query}%")
    # 値を取得する
    @tags = @tags.pluck(:name)
    render json: @tags
  end
end

Tagifyのデフォルトの文字列のフォーマットはJSONになるのでJSONをrenderします。
lineを入力して関連するタグを取得してみます。

23:44:29 web.1  | Started GET "/tags/search?query=line" for ::1 at 2023-08-31 23:44:29 +0900
23:44:29 web.1  | Processing by TagsController#search as */*
23:44:29 web.1  |   Parameters: {"query"=>"line"}
23:44:29 web.1  |   User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 7], ["LIMIT", 1]]
23:44:29 web.1  |   ↳ app/controllers/application_controller.rb:30:in `set_notification_object'
23:44:29 web.1  |   Tag Load (0.2ms)  SELECT "tags".* FROM "tags" WHERE (name LIKE '%line%')
23:44:29 web.1  |   ↳ app/controllers/tags_controller.rb:17:in `search'
23:44:29 web.1  | Completed 200 OK in 6ms (Views: 1.9ms | ActiveRecord: 0.4ms | Allocations: 3238)
23:44:29 web.1  | 
23:44:29 web.1  | 

ブラウザーのレスポンスはこちらになります:

レコードを取得できましたが、タグ名だけの配列が欲しいのでpluckメソッドを使ってタグの値を抽出します。

タグの検索機能ができました。
次にTagifyのインストールと設定を行います。

Tagifyをインストールする

インストール方法は公式ドキュメントまでご参考ください。

npm i @yaireo/tagify --save
@import "@yaireo/tagify/dist/tagify.css"
@import "@yaireo/tagify/src/tagify.scss"

Stimulusコントローラーを作成する

bin/rails g stimulus tag
      create  app/javascript/controllers/tag_controller.js
app/javascript/controllers/tag_controller.js
import { Controller } from "@hotwired/stimulus"
import Tagify from '@yaireo/tagify'

// Connects to data-controller="tag"
export default class extends Controller {
  connect() {
    var input = document.getElementById("tags-input");  
    console.log(input);
    new Tagify(input);
  }
}

データ属性を追加する

textareaにIDとコントローラー情報を追加します。
クラス名にtagifyを追加するとデフォリトのCSSを表示されます。

app/views/tags/_form.html.erb
...
  <%= form.text_area :tag_names, 
    id: "tags-input",
    value: item.tags.map(&:name), 
    placeholder: '[Enter]キーで区切って入力してください',
    class: "tagify"
    data: { controller: "tag" } %>

Tagifyを導入したことを確認します。

Image from Gyazo

次はオートコンプリート機能です。
ユーザーの入力によるタグの検索結果をTagifyのwhitelistに入れていきます。

タグを取得するリクエストを追加する

ドキュメントにサンプルコードがあるのでそのまま持ってきます。

app/javascript/controller/tags_controller.js
import { Controller } from "@hotwired/stimulus"
import Tagify from '@yaireo/tagify'

// Connects to data-controller="tag"
export default class extends Controller {
  connect() {
    var input = document.getElementById("tags-input");
  // tagifyの設定
    var tagify = new Tagify(input, {whitelist:[],
      dropdown:{
        enabled: 0,
        closeOnSelect: false,
      },
    }),
    controller; // for aborting the call

  // 入力で検索結果を取得する
    tagify.on('input', onInput)
    function onInput( e ){
    var value = e.detail.value
    tagify.whitelist = null // whitelistをリセットする

    controller && controller.abort()
    controller = new AbortController()

    fetch(`/tags/search?query=${value}`, {signal:controller.signal})
      .then(RES => RES.json())
      .then(function(newWhitelist){
        tagify.whitelist = newWhitelist // ホワイトリストに代入する
        tagify.loading(false).dropdown.show(value) // 関連する結果を表示させる
      })
    }
  }
}

https://github.com/yairEO/tagify#ajax-whitelist

取得されたタグをフロントで表示できるようになりました。

Image from Gyazo

タグの送信

タグがハッシュの配列として保存されるので値だけを抽出しRailsに送ります。
Tagifyではtagify.DOM.originalInput.valueメソッドが用意してくれてます。

console.log(tagify.DOM.originalInput.value)
=> ruby,rails

console.log(tagify.DOM.originalInput.value.split(','))
=> (2) ['ruby', 'rails']
0: "ruby"
1: "rails"
length: 2
[[Prototype]]: Array(0)
app/javascript/controllers/tag_controller.js
...
    var tagify = new Tagify(input, {whitelist:[],
      dropdown:{
        enabled: 0,
        classname: "",
        closeOnSelect: false,
      },
      # 値だけ取得する
      originalInputValueFormat: valuesArr => valuesArr.map(item => item.value)
    }),

終わりに

ライブラリーを使うことでタグのオートコンプリートができました。
ドキュメントが分かりやすかったので助かりました。
他にカスタマイズできるオプションやテンプレもたくさんあるのでまた試してみたいと思います。

Discussion