Stimulusの習作: Tagifyでのタグ入力(クライアント側)
外観
Stimulusの習作としてTagifyを用いたタグ入力を行います。入力したタグをRailsのバックエンドに保存したいと考えているため、画面のHTMLはRailsで生成しています。サーバ側と組み合わせた画面は次のようになります。
※今回やっていることとしては特にTagifyを動的に操作する必要がないため、別のページに簡素な実装方法を書きました。こちらと比較すると良いかもしれません。
環境
macOS 10.15.4
Ruby 2.6.5
Rails 6.0.2.2
Yarn 1.22.4
Node 13.12.0
stimulus@1.1.1
@yaireo/tagify@3.7.1
リポジトリ
参照
Tagifyの追加
yarnでTagifyを追加する。
SCSSファイルをコピーする。今回はstylesheet_pack_tagを用いませんでした。
yarn add @yaireo/tagify
cp node_modules/@yaireo/tagify/src/tagify.scss app/assets/stylesheets
Stimulusコントローラ
仕様は次のようになります。
Stimulusのコントローラは、TagifyControllerとする。
Stimulusのターゲットは、tagifyとtagNamesとする。
Stimulusのアクションは、使用しない。
HTMLの例
<div class="field" data-controller="tagify" data-whitelist="キャラクター,グルメ,...">
<label for="landmark_tag_names">タグ(5つまで)</label>
<input type="text" name="tagify" id="tagify" value="" data-target="tagify.tagify" />
<input data-target="tagify.tagNames" type="hidden" value="駅,公共施設,観光"
name="landmark[tag_names]" id="landmark_tag_names" />
</div>
ターゲットtagifyTargetは、Tagifyでのタグ入力で使用する。
従ってtagifyTargetは、<input>要素(または<textarea>要素)でなければならない。
Tagifyは、ターゲットtagifyTargetにShadow DOMを追加する。
従ってアプリは、Tagify生成後、tagifyTargetの内容に依存してはならない。Tagifyが提供するインタフェース経由で内容を操作する。
Tagifyは、新規にtags(クラスはtagify)要素とその配下にtag要素を生成し、タグ入力を可能とする。
上記のタグ入力は、スタイルシートtagify.scssを必要とする。
ターゲットtagNamesTargetは、Railsの隠しフィールドとする。
ターゲットtagNamesTargetで、カンマ区切りのタグ名をサーバとやり取りする。
ターゲットtagNamesTargetの値は、Tagifyのタグの初期値とする。
コントローラ要素のdata-whitelist属性からTagify用のwhitelistを取得する。
※名称に違和感がありますがホワイトリスト以外のタグも自由に入力できます。
実装
app/javascript/controllers/tagify_controller.js
tagifyをimportしてTagifyを参照可能にする。
Stimulusのターゲットを設定する。
import { Controller } from "stimulus"
import Tagify from '@yaireo/tagify'
export default class extends Controller {
static targets = [ "tagify", "tagNames" ]
tagifyTargetを渡してTagifyを生成する。
data-whitelistをwhitelistに設定する。
タグの最大数を5つに設定した。
ドロップダウンの設定はREADME.mdのSuggestions selectboxを参考にした。
タグの追加/削除イベントで隠しフィールドにタグ名を保存する。
tagNamesTargetをaddTagsでタグに設定する。
connect() {
const whitelist = this.element.getAttribute('data-whitelist').split(',')
this.tagify = new Tagify(this.tagifyTarget, {
whitelist: whitelist,
maxTags: 5,
dropdown: {
classname: "color-blue",
enabled: 0,
maxItems: 30,
closeOnSelect: false,
highlightFirst: true,
},
})
this.tagify.on('add', e => this.saveTagNames(e.detail.tagify))
this.tagify.on('remove', e => this.saveTagNames(e.detail.tagify))
const tagNamesStr = this.tagNamesTarget.value
if (tagNamesStr.length > 0) {
this.tagify.addTags(tagNamesStr.split(','))
}
}
saveTagNames(tagify) {
this.tagNamesTarget.value = tagify.value.map(v => v.value)
}
切断されたら、Turbolinksでの冪等性を担保するために、
入力された全てのタグを削除する。
Tagifyが生成した要素を削除する。
disconnect() {
this.tagify.removeAllTags()
const classes = this.element.getElementsByClassName('tagify')
Array.from(classes).forEach(e => e.remove())
}
}
以上です。
付録
※こちらに変更(改良?)版を書きました。
Rails new
rails new KyotikaLandS
Landmark
Landmark scaffold
bin/rails g scaffold Landmark name hiragana latitude:float longitude:float url question answer1 answer2 answer3 correct:integer author
Landmark validation
vim db/migrate/*_create_landmarks.rb
t.string :name, null: false
t.string :hiragana, null: false
vim app/models/landmark.rb
validates :name, :hiragana, presence: true, uniqueness: true
validates :hiragana, format: { with: /\A([ぁ-ん]|ー)+\z/,
message: 'は、ひらがなを入力してください。' }
validates :correct, numericality: { greater_than_or_equal_to: 1,
less_than_or_equal_to: 3 }
bin/rails db:migrate
Landmark Seed
vim db/seeds.rb
landmark = Landmark.create!(
name: 'JR京都駅',
hiragana: 'じぇーあーるきょうとえき',
latitude: 34.9855,
longitude: 135.758,
url: 'https://ja.wikipedia.org/wiki/京都駅',
question: '日本一長いホームとして知られるJR京都駅のホームの長さは?',
answer1: '558m',
answer2: '672m',
answer3: '773m',
correct: 1,
author: '臼谷泰弘'
)
bin/rails db:seed
Tag
Tag scaffold
bin/rails g scaffold Tag name
Tag validation
vim db/migrate/*_create_tags.rb
t.string :name, null: false
vim app/models/tag.rb
validates :name, presence: true, uniqueness: true
bin/rails db:migrate
Tagging
Tagging scaffold
bin/rails g model Tagging landmark:belongs_to tag:belongs_to
Tagging relation
vim app/models/landmark.rb
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
vim app/models/tag.rb
has_many :taggings, dependent: :destroy
has_many :landmarks, through: :taggings
bin/rails db:migrate
Tagging seed
vim db/seeds.rb
landmark.tags.create!(
[
{ name: '駅' },
{ name: '公共施設' },
{ name: '観光' }
]
)
Tag.create!(
[
{ name: 'キャラクター' },
{ name: 'グルメ' },
{ name: '出町柳' },
{ name: '動物' },
{ name: '博物館' },
{ name: '商業施設' },
{ name: '国宝' },
{ name: '平家' },
{ name: '橋' },
{ name: '流鏑馬' },
{ name: '漫画' },
{ name: '神社' },
{ name: '祭り' },
{ name: '聖徳太子' },
{ name: '葵祭' },
{ name: '賀茂川' },
{ name: '重要文化財' },
{ name: '音楽' },
{ name: '鴨川' }
]
)
bin/rails db:seed:replant
Stimulus
bin/rails webpacker:install:stimulus
Add Tagify
yarn add @yaireo/tagify
cp node_modules/@yaireo/tagify/src/tagify.scss app/assets/stylesheets
Tagify MVC
app/javascript/controllers/tagify_controller.js
app/models/landmark.rb
app/views/landmarks/_form.html.erb
app/controllers/landmarks_controller.rb
この記事が気に入ったらサポートをしてみませんか?