JavaScript
Vue.js

Vue.jsで作るタグ入力フォーム

More than 3 years have passed since last update.


タグ入力フォームを作る

Webサービスでよくタグを入力する機能がよくあると思います。Qiitaにも入力するところがあると思います。Qiitaではタグを作ると入力した言語の背景がかわってとてもいい感じです。

自分でも作ってみたいと思い試行錯誤してみました。Qiitaとか参考にすればいいとあとで気づいたのであまり筋のいい解では無いと思いますが、こういう実装もあるんだよ程度に思って頂けると幸いです。


動作

動作の様子です。スタイルが適当なのは勘弁してください。

tag.gif

https://jsfiddle.net/tomato360/nj6c8f3o/29/

少しわかりにくいのですがエンターキーでタグを確定しています。となりのxを押すとタグが削除されます。


実装


<div class="text-area">
<div class="tag-list">
<span class="tag" v-for="tag in tags">
{{tag.name}}
<span class="delete" v-on:click="delete(this)">x</span>
</span>
</div>
<div class="input-area" contenteditable=true v-on:keydown.enter="decide_tag">
</div>
</div>

.text-area{

width: auto;
height: 20px;
background: white;
}

.tag-list{
display: inline-block;
margin-top: 0px;
}

.input-area{
display: inline-block;
width: 100px;
height: 20px;
background: white;
&:focus{
outline: 0;
}
}

.tag {
padding: 2px;
background: yellow;
}

.delete {
cursor: pointer;
}

Vue.jsを用いているのでディレクティブがついています。

キモはタグのリストと入力フォームを分けている点で、これでタグが増えたら入力フォームが自動的に右側にスライドします。

inputタグは使わずdivcontenteditabletrueにして入力を可能にしています。これだと入力フォームっぽくならないので全体を包んでいるタグの背景を白くしています。

$(document).ready(function() {

var tag = new Vue({
el: ".text-area",
data: {
tags: []
},
methods: {
decide_tag: function(e) {
this.tags.push({
name: e.target.textContent
});
e.target.textContent = "";
},
delete: function(vm) {
this.tags.splice(vm.$index,1);
}
}
});
});

Vueのほうは簡単に実装しています。タグの一覧をdataのプロパティに配列としてもって、エンターキーを押した時に発火するdecide_tagpushしています。タグを消すときはタグの配列をspliceして削除しています。

あんまりいけてないのはinputタグを使っていないのでv-modelが使えないことで、v-modelだとdecide_tage.target.textContentからデータをとらないですむはずです。本来はViewModelからViewを参照するのはよくないので設計がよくないと言えますね(ならやるなってね)。

どうしてinputを使いたくなかったかというとinputを使うならinputを覆うオーバレイにタグを描画してinputを隠す必要があるからで、何となくそれが気に入らなかったからです。とはいえそうすると設計的によくない実装になるので今となってはオーバレイする方法がよかったかもですね。

あとinputを使わなかったことでformsubmitが使えないです。タグを入力し次第保存する方針でなければinputを使った方が素直で良いですね。


おわり

以上Vue.jsで作るタグ入力フォームでした。自分で書いていてなんですけどあんまりいけてませんね。入力するところが右側にスライドするところなんかはうまくいったかなと思ったのですが。

もう少し筋の良いやり方でリベンジしたいところです。