スキップしてメイン コンテンツに移動

[Vue.js] シンプルなタグ入力フォーム

タイトル Vue.jsでタグ入力

Vue.jsでEnterキーを押すごとに、タグ付けが可能なコンポーネントを作りました。
何それ? って方は、以下のデモ動画を見てもらえれば、一発でどんな動きか分かると思います。

デモ動画
タグ入力フォームのデモ動画

早速、ソースコードおよび使い方について紹介します。

特徴

・ 使い方がシンプル(多分…)
・ 親コンポーネントとの双方向バインドに対応。
・ タグを消す×ボタンはSVGアイコンなので、画像ファイル等は不要。
・ CSSはemサイズ指定で作成しているので、フォントサイズは自由。
・ドラッグ&ドロップで、タグの並び替えが可能

ソースコード

ソースコードは、こちらのGitHubでも公開しています。

HTML

<template>
  <div class="tag-container cf">
    <div class="tag-label"
      :class="{ 'dragover': tag.over }"
      v-for="(tag, index) in tags" :key="index"
      draggable="true"
      @dragstart="dragstart(tag, $event)"
      @dragend="dragend"
      @dragenter.prevent="dragenter(tag)"
      @dragleave="dragleave(tag)"
      @dragover.prevent="dragover(tag)"
      @drop="drop(tag, $event)">
      <span class="tag-label-text">{{tag.text}}</span>
      <a href="#" class="tag-remove" @click.stop.prevent="remove(index)">
        <svg xmlns="http://www.w3.org/2000/svg" 
          xmlns:xlink="http://www.w3.org/1999/xlink" 
          viewBox="0 0 50 50" version="1.1" width="15px" height="15px">
          <g id="surface1">
            <path style=" " d="M 7.71875 6.28125 L 6.28125 7.71875 L 23.5625 25 L 6.28125 42.28125 L 7.71875 43.71875 L 25 26.4375 L 42.28125 43.71875 L 43.71875 42.28125 L 26.4375 25 L 43.71875 7.71875 L 42.28125 6.28125 L 25 23.5625 Z "/>
          </g>
        </svg>
      </a>
    </div>
    <div class="editor">
      <input class="input" 
        ref="input" 
        type="text"
        placeholder="ここに入力"
        @keyup.enter="enter($event.target)"
        @keypress="canEnter = true"/>
    </div>
  </div>
</template>

JavaScirpt

<script>
export default {
  props: {
    value: {
      type: String,
      required: true,
    },
  },
  data() {
    return { 
      tags: [],
      prevValue: "",
      canEnter: false,
      draggingItem: null,
    }
  },
  methods: {
    propToData() {
      this.tags.length = 0
      this.prevValue = this.value.split(/,/)
      this.value.split(/,/).forEach((str) => this.add(str))
    },
    enter(target) {
      //日本語の確定で EnterキーのKeyupが発生するのを抑止
      if (!this.canEnter) return

      if (typeof target.value === "string" && target.value.trim() != "") {
        this.add(target.value.trim().replace(/,/, ""))
        target.value = ""
      }
      this.canEnter = false
    },
    remove(index) {
      this.tags.splice(index, 1);
    },
    add(str) {
      this.tags.push( { text: str, index: this.tags.length -1, over: false } )
      //array.splice(1, 0, 'A');
    },
    sort() {
      this.tags.sort((a, b) => {
        return a.index < b.index ? -1 :
          a.index > b.index ? 1 : 0;
      })
    }, 
    dragstart(item, e) {
      this.draggingItem = item
      e.target.style.opacity = 0.5
    },
    dragend(e) {
      e.target.style.opacity = 1;
      this.tags.forEach(n => { n.over = false })
    },
    dragenter(item) {
      item.over = true
    },
    dragover(item) {
      item.over = true
    },  
    dragleave(item) {
      item.over = false
    },
    drop(item, e) {
      const index = item.index
      this.tags.forEach(n => { if (n.index >= index) n.index++ })
      this.draggingItem.index = index
      this.sort()
      this.draggingItem = null
    }
  },
  watch: {
    value: function(newVal, oldVal) {
      if (this.tags.join(",") != newVal) {
        this.propToData()
      }
    }
  },
  mounted() {
    this.propToData()
  },
  updated() {
    let newVal = this.tags.map(tag => tag.text).join(",")
    if (!this.prevValue || this.prevValue != newVal) {
      this.$emit('input', newVal)
      this.prevValue = newVal
    }
  }
}
</script>

CSS

<style scoped>
.tag-container {
  padding: 5px 5px 0 5px;
  border: 1px solid #ccc;
  margin: 0px;
  margin-left: -5px;
  font-size: 15px;
}
.tag-container .tag-label,
.tag-container .editor {
  float: left;
}
.tag-container .tag-label {
  background: #EBF4FB;
  padding: 0 2.1em 0 12px;
  margin: 0 5px 5px 0;
  height: 2em;
  border-radius: 1em;
  box-sizing: border-box;
  position: relative;
}
.tag-label-text {
  display: inline-block;
  height: 100%;
  margin: 0;
  padding: .5em 0 0 0;
  line-height: 1;
}
.tag-container a.tag-remove {
  height: 100%;
  width: 2em;
  display: block;
  position: absolute;
  border-radius: 2em;
  right: 0;
  top: 0;
  margin: 0;
  padding: 0;
}
.tag-container a.tag-remove:hover {
  background: #bbd8f1;
}
.tag-container .tag-label svg {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
}
.tag-container .editor .input {
  border: 0px;
  outline: 0;
  background: transparent;
  padding: 3px 4px;
  min-width: 5em;
  width: auto;
}
.cf::after {
  content: "";
  clear: both;
  display: table;
}

.tag-container .tag-label.dragover {
  margin-left: 10px;
}
.tag-container .tag-label.dragover::before {
  width: 4px;
  height: 1.6em;
  background: #333;
  display: block;
  content: "";
  position: absolute;
  left:-10px; 
  top: 0.2em;
  background: rgb(109, 109, 112);
  border-radius: 1em;
}
</style>

使い方

(1) タグ入力フォームを、テンプレートのHTMLに配置します。v-modelに双方向バインドを行うプロパティを指定します。
(2) タグ入力フォームのソースコードをインポートします。
(3) 子コンポーネントに登録します。
(4) バインドするプロパティには、カンマ区切りの文字列を設定します。

<template>
  <section class="container" style="width:600px">
    <!--(1)-->
    <tag-input v-model="tag_str"/>
  </section>
</template>

<script>
import TagInput from '~/components/tagInput.vue'  //(2)

export default {
  components: {
    TagInput  //(3)
  },
  data() {
    return { 
      tag_str: "Javascript,C#,Java"  //(4)
    }
  },
}
</script>

さいごに

タグを入力する以外、何も他には機能がなく、シンプルで気持ちがいいですね!
でも、これで終わりにせず、今後は色々と機能追加をしていきたいと思います。
今の構想は、一括タグ削除機能や、タグ入力時に候補検索できるオートコンプリート機能などを今後作って行きたいと思っています。

コメント

このブログの人気の投稿

axiosの使い方まとめ (GET/POST/例外処理)

axiosの使い方まとめ (GET/POST/例外処理)最近何かとよく使うJavaScriptでAJAX通信を行うaxiosについて、簡単に使い方をまとめました。GETリクエストをaxiosで送るまずはGETリクエストをaxiosで送る方法です。const res =await axios.get('/users') console.log(res.data)分割代入の記法を使うと、以下のようにも書けますconst{data}=await axios.get('/users') console.log(data)クエリパラメータ (URLパラメータ)を指定クエリパラメータを指定する方法は2つあります。1つ目は、axios.getに指定するURLに直接記述する方法です。axios.get('/user?id=123')2つめは、axios.getの第2引数に、オプション指定する方法です。axios.get('/user',{ params:{ id:123}})POSTリクエストをaxiosで送る次はPOSTリクエストをaxiosで送る方法です。JSON形式でPOSTするJSON形式でPOSTする場合は、axios.postの第2引数に、送信するデータをJavaScriptオブジェクトで指定します。const res =await axios.post('/user',{ id:123, name:'Yamada Tarou'})application/x-www-form-urlencoded形式でPOSTするapplication/x-www-form-urlencoded形式でPOSTする場合は、URLSearchParamsを使います。var params =newURLSearchParams() params.append('id',123) params.append('name','Yamada Tarou')const res =await axios.post('/user', params)スポンサーリンク axios でファイルをアップロードする画像などのファイルを、…

[VB, C#] Windows 8, Window 10 で ImeModeが制御できない問題を解決する

[VB, C#] Windows 8, Window 10 で ImeModeが制御できない問題を解決するタイトルの通りですが、Windows 8 以降では Windows Form アプリケーションで、コントロールの ImeMode に Katakana や KatakanaHalf を設定しても、カタカナになってくれません。なぜ ImeMode が効かないのか?Windows 8 以降、IME Mode の切り替えは、ユーザー単位で切り替わるようになった為、アプリから IME Mode 制御が出来ないようになりました。
(IME をON にした場合、常に ひらがな モードになます)※ Windows 7までは、IME Modeの切り替えはアプリ単位で行われていた為、問題なくアプリから IME制御が行えました。スポンサーリンク 対処方法Windows 8 以降、IMEの制御は、InputScope クラスの利用が推奨されています。
しかし、InputScope クラスは、WPF、Windows ストアアプリでしか使えない為、Windows Formアプリでは使用できません。
(Windows Form はもう使うな!という事でしょうか (涙) )結論としては、コントールパネルの設定で、IMEの制御をユーザ単位から アプリ単位に変更する事ができます。
これで、Windows Formアプリでも 従来通りIMEの制御を行う事が出来ます。おわりにこの方法だと、アプリをインストールする端末すべてに設定が必要となり、とっても面倒です。。。
しかし、今の所これしか方法がない状態です。
これからは Windows Formではなく、WPFや Windows ストアアプリで作れという事ですかね (^^;)

MailKitの使い方! エンコーディング指定や添付ファイをメールで送信する方法[C#/VB Tips]

MailKitの使い方! エンコーディング指定や添付ファイをメールで送信する方法[C#/VB Tips]MailKitを使ってメールを送るサンプルコードです。(C#)UTF8/iso-2022-jpのエンコーディング指定、GMail/YahooのSMTPサーバで送るなど、4つのサンプルコードでMailKitの使い方を紹介します。MailKitって何?2017年に.NET標準のSystem.Net.Mail.SmtpClientが廃止予定となり、Microsoftより今後はオープンソースライブラリである、MailKitに置き換えるとアナウンスがありました。既にSmtpClientは非推奨になっており、今後は廃止されていきます。現在、SmtpClientを使用したソースコードには、Visual StudioからMailKitを使うよう警告が出るようになっています。さっそく、MailKitを使ってメールを送信するサンプルコードを作っていきます。UTF8でメールを送信文字エンコーディングを、UTF8でメールを送信するサンプルコードです。
MailKitは、デフォルトの文字エンコーディングがUTF8なっている為、シンプルなコードでメールを送信する事ができます。var host ="<smtp server name>"; var port =25;// or 587using(var smtp =new MailKit.Net.Smtp.SmtpClient()){//SMTPサーバに接続する smtp.Connect(host, port, MailKit.Security.SecureSocketOptions.Auto);//認証が必要な場合は、以下のコメントを解除//smtp.Authenticate("<id>", "<password>");//送信するメールを作成する var mail =new MimeKit.MimeMessage(); var builder =new MimeKit.BodyBuilder(); mail.From.Add(new MimeKit.MailboxAddress("",&quo…