【Vue】ディレクティブ(フォーム)

双方向データバインディング

テンプレートとデータオブジェクトの双方向のデータを反映することを双方向データバインディングと呼ぶ

以下のソースコードではテキストボックスに入力された名前に応じて「こんにちは、○○さん!」という挨拶を表示する
※v-modelを利用した場合、テキストボックスの初期値は紐づいたプロパティの値となり、value属性は無視される

<div id="app">
  <form>
    <label for="name">氏名:</label>
    <input type="text" id="name" v-model="myName" />
  </form>
  <div>こんにちは、{{ myName }}さん!</div>
</div>
new Vue({
  el: '#app',
  data: {
    myName: '匿名',
  },
})

ラジオボタン

ラジオボタンを実装する場合、すべての選択オプションに対して同一のv-modelを渡す
これによってv-modelの値とvalue属性が等しいオプションが選択状態になる

  <div id="app">
    <form>
      <label for="dog">いぬ</label>
      <input type="radio" id="dog" value="いぬ" v-model="pet" />
      <br />
      <label for="dog">ねこ</label>
      <input type="radio" id="cat" value="ねこ" v-model="pet" />
      <br />
      <label for="dog">その他</label>
      <input type="radio" id="other" value="その他" v-model="pet" />
      <br />
    </form>
    <p>ペット:{{ pet }}</p>
  </div>
new Vue({
  el: '#app',
  data: {
    pet: 'いぬ',
  },
})

チェックボックス(単一)

チェックボックスは単一でon/offを表す場合と、リストで複数選択オプションを表す場合とがある
以下はon/offを表す書き方

  <div id="app">
    <form>
      <label for="agree">同意する</label>
      <input type="checkbox" id="agree" v-model="agree" />
      <!-- bool値以外で書く場合
      <input type="checkbox" id="agree" v-model="agree" true-value="yes" false-type="no" />-->
    </form>
    <div>回答:{{ agree }}</div>
  </div>
new Vue({
  el: '#app',
  data: {
    agree: true,
  },
})

チェックボックス(複数)

複数のチェックボックスを並べる場合には、ラジオボタンの場合と同じく、これらすべてに対して同一のv-modelを渡す

<div id="app">
  <form>
    <div>お使いのOSは?</div>
    <label for="windows">Windows</label>
    <input type="checkbox" id="windows" value="Windows" v-model="os" />
    <label for="linux">Linux</label>
    <input type="checkbox" id="linux" value="Linux" v-model="os" />
    <label for="mac">macOS</label>
    <input type="checkbox" id="macOS" value="macOS" v-model="os" />
  </form>
  <div>回答:{{ os }}</div>
</div>
new Vue({
  el: '#app',
  data: {
    os: ['Windows', 'macOS'],
  },
})

選択ボックス

select要素にv-modelを指定するだけ

<div id="app">
  <form>
    <label for="os">お使いのOSは?</label>
    <select v-model="os" multiple size="3">
      <option>Windows</option>
      <option>Linux</option>
      <option>macOS</option>
    </select>
  </form>
  <div>回答:{{ os }}</div>
</div>
new Vue({
  el: '#app',
  data: {
    os: '',
  },
})
補足:オブジェクトをバインドする

ラジオボタンや選択ボックスには文字列だけでなくオブジェクトを渡すことができる

<div id="app">
  <form>
    <label for="million">百万:</label>
    <input type="radio" id="million" v-model="unit" v-on:change="onchange"
      v-bind:value="{name:'百万', size: 1000000}" /><br>
    <label for="million">十億:</label>
    <input type="radio" id="billion" v-model="unit" v-on:change="onchange"
      v-bind:value="{name:'十億', size: 1000000000}" /><br>
    <label for="million">一兆:</label>
    <input type="radio" id="trillion" v-model="unit" v-on:change="onchange"
      v-bind:value="{name:'一兆', size: 1000000000000}" /><br>
  </form>
</div>
new Vue({
  el: '#app',
  data: {
    unit: {},
  },
  methods: {
    onchange: function () {
      console.log(this.unit.name + ':' + this.unit.size)
    },
  },
})

ファイル入力ボックス

他のフォーム要素と異なり、ファイル入力ボックスはユーザが指定した値をアプリが受け取るだけで、アプリが特定の値(ファイル)を指定することはできない
input type="file"要素には、後でイベントハンドラーからアクセスできるように、ref属性で名前を付けておく(①) ref属性はVueで予約された特殊な属性で、ここで命名された要素は、イベントハンドラーなどからthis.$refs.hogeの形式でアクセスできる(②)

<div id="app">
  <form>
    <input ref="upfile" type="file" v-on:change="onchange" /> <!-- ① -->
  </form>
  <div>{{ message }}</div>
</div>
new Vue({
  el: '#app',
  data: {
    message: '',
  },
  methods: {
    onchange: function () {  // ②
      // アップロードファイルを準備
      let fl = this.$refs.upfile.files[0]
      let deta = new FormData()
      deta.append('upfile', fl, fl.name)
      // サーバにデータを送信
      fetch('upload.php', {
        method: 'POST',
        body: deta,
      })
        // 成功時には結果を表示
        .then(function (response) {
          return response.text()
        })
        .then(function (text) {
          this.message = text
        })
        .catch(function (error) {
          windows.alert('Error' + ErrorEvent.message)
        })
    },
  },
})

バインドの動作オプションを設定する

ディレクティブの後ろに修飾子を付与することで、バインド時の挙動を細かく制御できる

入力値を数値としてバインドする.number修飾子

ユーザからの入力値は既定で文字列とみなされるが、.number修飾子を利用することでコード側での数値変換が不要になる

<div id="app">
  <label for="temperature">サウナの温度</label>
  <input type="text" id="temperature" v-model.number="temperature" v-on:change="onchange" />
</div>
new Vue({
  el: '#app',
  data: {
    temperature: 0,
  },
  methods: {
    // 入力値を小数点以下1位に丸目、ログ出力
    onchange: function () {
      console.log(this.temperature.toFixed(1))
    },
  },
})

入力値の前後の空白を除去する.trim修飾子

.trim修飾子を利用することで、入力値をプロパティにバインドする前に、前後の空白を除去できる

<div id="app">
  <label for="memo">メモ:</label>
  <input type="text" id="memo" v-model.trim="memo" v-on:change="onchange" />
</div>
new Vue({
  el: '#app',
  data: {
    memo: '',
  },
  methods: {
    // 入力値をログ出力
    onchange: function () {
      console.log('入力値は「' + this.memo + '」です。')
    },
  },
})

バインドのタイミングを遅延させる.lazy修飾子

v-modelによるバインドの既定タイミングはinputイベントの発生時 .lazy修飾子を利用することで、このタイミングをフォーム要素からフォーカスが移動したタイミングにすることができる

<div id="app">
  <form>
    <label for="name">氏名:</label>
    <input type="text" id="name" v-model.lazy="myName" />
  </form>
  <div>こんにちは、{{ myName }}さん!</div>
</div>
new Vue({
  el: '#app',
  data: {
    myName: '匿名',
  },
})

双方向データバインドのカスタマイズ

双方向データバインドを実装するには通常v-modelを利用するが、入力された値をプロパティにバインドする際になんらかの処理を挟みたい場合はv-bind:valueやv-on:inputの組み合わせを利用する
以下は、入力されたメールアドレス(セミコロン区切り)を分割し、配列としてmailsプロパティに反映させる例

<div id="app">
  <form>
    <label for="mail">メールアドレス:</label>
    <!--
      ①v-bind:valueで入力されたセミコロン区切りの文字列をsplitで分割し、mailsプロパティに反映
      ②v-on:inputで再びjoinメソッドで連結した上で、<textarea>要素にバインド
    -->
    <textarea id="mail" v-bind:value="mails.join(';')" v-on:input="mails=$event.target.value.split(';')"></textarea>
  </form>
  <ul>
    <li v-for="mail in mails">
      {{ mail }}
    </li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    mails: [],
  },
})