作りたいものがありすぎる

40歳を過ぎてプログラミングを始めた人の顛末とこれからなど

【輪読会資料】基礎から学ぶVue.js CHAPTER3 イベントとフォーム入力の受け取り 読書メモ

以下の記事は2019/2/14 コワーキングスペース秋葉原Weeybleで行われる輪読会
[秋葉原] 基礎から学ぶVue.js輪読会 ch3 イベントとフォーム入力 (初心者歓迎!)のための読書メモとなります。
以下の書籍の CHAPTER3 イベントとフォーム入力の受け取り のメモです。

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

CHAPTER3 イベントとフォーム入力の受け取り

ちなみに Weeybleで去年同じ本の輪読会で使ったドキュメント、およびソースコードは以下にありました。

GitHub yasugahira0810/vuejs_chapter3

書籍用のサイトのCHAPTER3記述ページ(サンプルコード有り)

これまでの輪読会資料

CAHPTER 1
【輪読会資料】基礎から学ぶVue.js CHAPTER1 Vue.jsとフレームワークの基礎知識 読書メモ - 作りたいものがありすぎる

CHAPTER 2
【輪読会資料】基礎から学ぶVue.js CHAPTER2 データの登録と更新 - Qiita

SECTION 13 イベントハンドリング

イベントハンドラ
これまでのサンプルのボタンに出てた v-onの事をここでは解説する

イベントに紐づける処理の内容をこの本では「イベントハンドラと呼び
イベントハンドラとイベントを紐づけることを「ハンドル」と呼ぶ
イベントはmousewheelIE9では動かないものもあるので注意

<button v-on:click="doRemove(index)">モンスターを削除</button>

@で記述も可能

<button @click="doRemove(index)">モンスターを削除</button>
new Vue({
    el: '#app',
    data: {
    },
    methods: {
        doRemove: function (index) { // ボタンクリックでこの処理が走る
            this.list.splice(index, 1)
        }
    }
})

click同様ブラウザが対応していれば以下のイベントも使える

  • scroll
  • mousewheel

フォーム入力の取得

v-onディレクティブで入力内容を確認してからデータに代入することができる

<input v-bind:value="message" v-on:change="handleInput">
new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue.js',
    },
    methods: {
        handleInput: function (event) {
            // 代入前に何か処理を行う…
            // バリデード処理とかできるのかな?
            this.message = event.target.value
        }
    }
})

イベント修飾子

click などのDOMのふるまいを変更する

  • .stop event.stopPropagation(); イベント伝播,バブリングを止める
  • .prevment event.preventDefault(); 禁止操作の指定 リンク操作、submitの処理をキャンセル
  • .capture キャプチャーモードDOMイベントをハンドルする
  • .self
  • .native
  • .once
  • .passive { passive: true } でDOMイベントはハンドルする

クリックイベント マウスボタンを指定できる

  • .left
  • .right
  • .middle

作例

<!-- いずれもhandlerメソッドでマウス右クリックでconsole.logにmouse event のlogを出力する -->

<div v-on:click.right="handler">example</div>
<!-- こっちは`.prevment`修飾子で右クリックメニューの表示を禁止している -->
<div v-on:click.right.prevent="handler">example</div>
new Vue({
    el: '#app',
    methods: {
        handler: function (comment) {
            console.log(comment)
        }
    }
})

Extra DOMイベント伝播,バブリングについて

そもそもJavaScriptのバブリングの概念を知っておく必要あり
DOMイベントのキャプチャ/バブリングを整理する 〜 JSおくのほそ道 #017

その上でイベント修飾子を付与することでバブリングを制御できる。

入れ子のDOMイベントの発生順序は JSの addEventListenerには第三引数に省略可能でデフォルトはfalseの値がある。
これをuseCaptureという

<div id="outer">
    <div id="inner" align="center"></div>
</div>
function out(s) {return function() {console.log(s);}}
document.getElementById('outer').addEventListener('click', out('outer'), false); // ←コレ
document.getElementById('inner').addEventListener('click', out('inner'));

#outer,.addEventListenerの第三引数を false または省略した場合、innerのイベントが先に発火する。
逆にtureにすればouterが先に発火する

結果として、これを理解していないでアッチコッチにイベント仕込むと、親要素にイベントが伝播しまくって困った事になるらしい。
そこでVue.jsでは前述のイベント修飾子を使って伝播の制御を行う、という事らしい

では以下のJSを元に書くイベント修飾子の動きを確認してゆく

new Vue({
    el: '#app',
    methods: {
        handler: function (comment) {
            console.log(comment)
        }
    }
})

.stop

event.stopPropagation(); イベント伝播,バブリングを止める
div2クリックでdiv2のみが出力

<div v-on:click="handler('div1')">
  div1
  <a href="#top" v-on:click.stop="handler('div2')">div2</a>
</div>

.prevent

event.preventDefault(); 禁止操作の指定 リンク操作、submitの処理をキャンセル
div2クリックでdiv2,div1と出力(操作の禁止をするのみなので伝播は通常通り起こるという事?)

<div v-on:click="handler('div1')">
  div1
  <a href="#top" v-on:click.prevent="handler('div2')">div2</a>
</div>

.capture

キャプチャーモードでイベントを発生させる バブリングモードのイベントよりも先に発生する
div3クリックでdiv1,div3,div2の順で出力

<div v-on:click.capture="handler('div1')">
  div1
  <div v-on:click="handler('div2')">
    div2
    <div v-on:click="handler('div3')">div3</div>
  </div>
</div>

.self

evant.targetが自分自身の時だけハンドラが呼び出される

<div class="overlay" v-on:click.self="close">div</div>

.native

直接イベントを発火させたい場合に使う 詳細はCAPTER5に

<!-- コンポーネントをクリックするとハンドラが呼び出される -->
<my-component v-on:click.native="handler"></my-component>
<!-- コンポーネントをクリックしてもハンドラは呼び出されない -->
<my-component v-on:click="handler"></my-component>

.passive

event.prevmentDefault()を呼び出さない事を明示的にする
.preventとの併用はNG
モバイル環境でのスクロールカク追記を防ぐ等に使用

キー修飾子

キーボード入力時に呼び出される様になる修飾子,キーコードか、キー指定でもOK

<!-- どちらもEnterキーを表す -->
<input v-on:keydown.13="handler">
<input v-on:keydown.enter="handler">

システム修飾子

キーが押されている場合のみハンドラが呼び出される
以下はshiftキーの例

<button v-on:click.shift="doDelete"></button>

その他詳細はVue.js公式ガイド「イベントハンドリング」「システム修飾子キー」を参照のこと

SECTION14 フォーム入力バインディング

フォームの入力や選択値を、データを同期する 「双方向データバインディング にはv-modelディテクティブを使う

v-modelの使い方

テキストフォームをmessageプロパティとバインディングした例

<div id="app">
    <input v-model="message">
    <p>{{ message }}</p>
</div>
new Vue({
  el: '#app',
  data: {
    message: 'Hello!'
  }
})

Vue.jsの双方向データディバイディング

入力した文字をデータに反映したい場合は、入力イベントをハンドルして取得したデータをリアクティブデータに代入する必要がある。

this.message = event.taget.value // ここでデータが書き換わる

一連の例に出て来るmessageを使用した処理は良く行われる

v-modeディレクティブはDOMのデータバインディングと要素から取得したデータをリアクティブにするための鉄板構文らしい。

v-modelで受け取りデータの型

基本、入力フォームは文字列型、複数選択フォームは配列型となるが、値にデータバインディングを使用した場合、値の型はバインドされているデータによって変わる。

複数行テキスト

文字列となる。

<textarea v-model="message"></textarea>
<pre>{{ message }}</pre>
new Vue({
  el: '#app',
  data: {
    message: 'Hello!'
  }
})

チェックボックス

単数の場合、は単純に bool

<label>
  <input type="checkbox" v-model="val"> {{ val }}
</label>
new Vue({
  el: '#app',
  data: {
    val: true
  }
})

複数要素は配列、各要素にvalue属性を設定

<label><input type="checkbox" v-model="val" value="A"> A</label>
<label><input type="checkbox" v-model="val" value="B"> B</label>
<label><input type="checkbox" v-model="val" value="C"> C</label>
<p>{{ val }}</p>
new Vue({
  el: '#app',
  data: {
    val: []
  }
})

AとCの選択では ["A", "C"]となる

ラジオボタン

デフォルトは文字列

<label><input type="radio" value="a" v-model="val"> A</label>
<label><input type="radio" value="b" v-model="val"> B</label>
<label><input type="radio" value="c" v-model="val"> C</label>
<p>{{ val }}</p>
new Vue({
  el: '#app',
  data: {
    val: ''
  }
})

セレクトボックス

単一選択プルダウン形式
デフォルト文字列

<select v-model="val">
  <option disabled="disabled">選択してください</option>
  <option value="a">A</option>
  <option value="b">B</option>
  <option value="c">C</option>
</select>
<p>{{ val }}</p>
new Vue({
  el: '#app',
  data: {
    val: ''
  }
})

複数選択リスト形式

<select v-model="val" multiple>
  <option value="a">A</option>
  <option value="b">B</option>
  <option value="c">C</option>
</select>
<p>{{ val }}</p>
new Vue({
  el: '#app',
  data: {
    val: []
  }
})

AとCの選択では ["A", "C"]となる

画像ファイル

v-modelは使用できない。リアクティブにするならchangeイベントをハンドルする

<input type="file" v-on:change="handleChange">
<div v-if="preview"><img v-bind:src="preview"></div>
new Vue({
  el: '#app',
  data: {
    preview: ''
  },
  methods: {
    handleChange: function (event) {
      var file = event.target.files[0]
      if (file && file.type.match(/^image\/(png|jpeg)$/)) {
        this.preview = window.URL.createObjectURL(file)
      }
    }
  }
})

画像選択するとプレビューが出る!カッコイイ!

その他入力タイプ

range,colorHTML5の入力タイプも使える

横スライドレンジの数値が出る奴

<input type="range" v-model.number="val">{{ val }}
new Vue({
  el: '#app',
  data: {
    val: 50
  }
})

修飾子

v-modelにくっつく奴

修飾子 作用
.lazy inputの代わりにchangeイベントはハンドルする
.number 値を数値に変換する
.trim 値の余分なスペースを削除する

.numberの使用例
テキストフォームに入った値はtype="number"としても文字列となる。
だが、これで数値として取得することが出来る。

<input type="text" v-model.number="price"> {{ price }}
new Vue({
el: '#app',
    data: {
    price: 50
    }
})

SECTION 15 マウント要素外のイベントと操作

v-onはDOMのwindow,bodyでは使用できない為、それらを扱いたい場合はJS純正のaddEventLisnerメソッドを使う事になる。注意点として、不要になっても自動的に解除されないので、不要になった際はフック(ライフサイクルフックCAPTER1の最後の奴)を使って解除する必要がある。

スクロールイベントの取得

発生頻度の高いイベント等は、タイマーを使用して処理の実行頻度を抑えると良い。
以下はwindowのスクロールイベントを200ms間隔でwindow.scrollYプロパティを更新する例
これを応用して、サイドバーを画面に常に固定したり、スクロールすると表示を変化させるメニュー等に使用可能。

<header v-bind:class="{ compact: scrollY > 200 }">
  200pxより下にスクロールしたら .compact を付与する
</header>
new Vue({
  el: '#app',
  data: {
    scrollY: 0,
    timer: null
  },
  created: function () {
    // ハンドラを登録
    window.addEventListener('scroll', this.handleScroll)
  },
  // CAPTER1最後のライフサイクルダイアグラムの beforeDestroy
  beforeDestroy: function () {
    // ハンドラを解除(コンポーネントやSPAの場合忘れずに!)
    window.removeEventListener('scroll', this.handleScroll)
  },
  methods: {
    // 違和感のない程度に200ms間隔でscrollデータを更新する例
    handleScroll: function () {
      if (this.timer === null) {
        this.timer = setTimeout(function () {
          this.scrollY = window.scrollY
          clearTimeout(this.timer)
          this.timer = null
        }.bind(this), 200)
      }
    }
  }
})

bodyに適当な要素を縦長に書いてから開発コンソールのElementsを開いて確認してみる
スクロール前

<header class="">
  200pxより下にスクロールしたら .compact を付与する
</header>

スクロール後

<header class="compact">
  200pxより下にスクロールしたら .compact を付与する
</header>

当然、JS内の最後の値200の数値を大きくすると反応はニブくなる。

スムーススクロールの実装

よくある『ページTOP』で滑らかに移動する奴はwindowオブジェクトを操作している、ライブラリを使えば簡単に実装できる、ここでは「Smooth Scroll」を使った例を示す。
GitHub Smooth Scroll

<script src="https://cdn.jsdelivr.net/npm/smooth-scroll@12.1.5"></script>
<div id="app">
  <div class="content">...</div>
  <div v-on:click="scrollTop">
    ページ上部へ移動
  </div>
</div>
// ここでSmoothScrollを変数に入れている
var scroll = new SmoothScroll()
new Vue({
    el: '#app',
    methods: {
        scrollTop: function () {
            // scrollTopのバインドでSmoothScrollのメソッド animateScroll() を呼んでいる
            // 引数に画面最上部からの位置を指定できる
            scroll.animateScroll(0)
        }
    }
})

COLUMN Vue.js以外からのイベントの読み取り

プラグインの実装等で、Vue.js以外のDOM操作ライブラリを使わざるを得ない場合、JSのdispatchEventを使ってイベント検知が出来る

以下はjQueryvalメソッドと絡めた例

<div id="app">
  <input id="message" v-on:input="handleInput">
  <button data-update="jQuery!">jQueryからの更新</button>
</div>
<!-- html内でjQueryのCDNを別途読み込むこと -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>  
$(document).on('click', '[data-update]', function () {
    $('#message').val($(this).attr('data-update'))
    // 入力値を更新したらイベントを発生させる
    $('#message')[0].dispatchEvent(new Event('input'))
})
new Vue({
    el: '#app',
    methods: {
        handleInput: function (event) {
            console.log(event.target.value)
        }
    }
})

まとめ