初心者による DOM イベントを使った処理-2

#イベントリスナー/ハンドラーの削除
イベントリスナー/イベントハンドラーは設定することしかできなかったが、削除もできる。

##イベントハンドラーの削除
イベントハンドラーはイベントリスナーよりも簡単に削除できる。これは、onXXXXメソッドに対してnull値を代入するだけである。

```html:sample.html
<form>
 <input id="btn" type="button" value="ダイアログ表示" />
</form>
```

```javascript:sample.js
window.addEventListener('DOMContentLoaded', function() {
 let btn = document.getElementById('btn')

 //イベントハンドラーを登録
 btn.onCLick = function() {
  window.alert('Hello!, Javascript!')
 }
 //イベントハンドラーを破棄
 btn.onClick = null
})
```

##イベントリスナーの削除
イベントリスナーの削除には、removeEventListener()メソッドを使う。

```javascript:sample.js
elem.removeEventListener(type, listener, capture)
 //elem : 要素オブジェクト type : イベントの種類 
 //listener : 削除するイベントリスナー capture : イベントの伝搬方向
```
これを使って書き直すと、

```html:saple.html
<form>
 <input id="btn" type="button" value="ダイアログ表示" />
</form>
```

```javascript:sample.js
document.addEventListener('DOMContentLoaded', function() {
 let btn = document.getElementById('btn')
 let listener = function() {
  window.alert('Hello, Javascript')
 }

 //イベントリスナーの設定
 btn.addEventLister('click', listener, false)
 //イベントリスナーの削除
 btn.removeEventListener('click', listener, false)
}, false)
```

#イベントオブジェクト
イベントリスナーやイベントハンドラーはイベントオブジェクトと呼ばれるオブジェクトを引数として受け取ります。

イベントオブジェクトを受け取るには、イベントリスナーに引数を指定するだけです。引数名は、慣例として[e]を使います。イベントオブジェクトを使用しない場合は省略しても構わない。

```html:sample.html
<form>
 <input id="btn" type="button" value="クリック" />
</form>
```

```javascript:sample.js
document.addEventListener('DOMContentLoaded', function(){
 document.getElementById('btn').addEventListener('click', function(e) {
  let target = e.target
  console.log('発生元：' + target.nodeName + '/' + target.id)
  console.log('種類：' + e.target)
 }, false)
}, false)

//出力
//発生元：INPUT/btn
//種類：click
```

##イベント発生時のマウス情報の取得
click / mousemoveなどのイベント発生時のマウスポインターの座標を取得するには、次のようなプロパティを使います。

```
screenX / screenY : スクリーン上の座標
pageX / pageY : ページ上の座標
clientX / clientY : 表示領域上の座標
offsetX / offsetY : 要素領域上の座標
```
たとえば、

```html:sample.html
<div id="main" style=
 "position: absolute; margin: 50px; 
  top: 50px; left: 50px; width: 200px; height: 200px: 
  border: 1px solid Black"></div>
```

```javascript:sample.js
document.addEventListener('DOMContentLoaded', function() {
 let main = document.getElementById('main')
 main.addEvenetListener('mousemove', function(e) {
  main.innerHTML = 
  'screen' + e.screenX + '/' + e.screenY + '<br />'
  + 'page' + e.pageX + '/' + e.pageY + '<br />'
  +  'client' + e.clientX + '/' + e.clientY + '<br />'
  +  'offset' + e.offsetX + '/' + e.offsetY + '<br />'
 }, false)
}, false)
```

##イベント発生時のキーの情報を取得する
推されたキーの種類を取得するには、ketpress/keydownなどのキーイベントを使います。

```html:sample..html
<form>
 <label for="key">キー入力：</label>
 <input id="key" type="text" size="10" />
</form>
```

```javascript:sample.js
document.addEventListener('DOMContentLoaded', function() {
 document.getElementById('key').addEventListener('keydown', function(e) {
  console.log('キーコード：' + e.keyCode)
 }, false)
}, false)
```

ここでは押されたキーコードを出力するために、keyCodeを使っているが他にも以下のようなプロパティがある。

```
button : マウスのどのボタンが押されているか。
key : 押されたキーの値
keyCode : 押されたキーのコード
altKey : Altキーが押されているか
ctrlKey : Ctrlキーが押されているか
shiftKey : Shiftキーが押されているか
metaKey : Metaキーが押されているか
```

#イベント処理をキャンセルする
イベントオブジェクトのstopPropagation/stopImmediatePropagation/preventDefaultメソッドを使うことで、イベント処理を途中でキャンセルできます。

##イベントの伝搬
「イベントが発生したら対応するイベントリスナーが呼び出される」がじつは、イベントが特定の要素に到達するまでにまいくつかのプロセスがある。

```
1. キャプチャフェーズ
windowsオブジェクトから下位要素にイベントを伝搬
2. ターゲットフェーズ
イベントの発生元(要素)を特定
3. バブリングフェーズ
下位要素で発生したイベントを上位要素に伝搬
```

ここで押さえておきたいのが、「イベントリスナーはイベント発生元の要素だけで実行されるわけではない」という点だ。キャプチャ/バブリングフェーズの過程で、対応するイベントリスナーが存在する場合には、それらも順に実行される。

```html:sample.html
<div id="outer">
 <p>outer要素</p>
 <a id="inner" href="http://www.giucgSD.sample">inner要素</p>
</div>
```

```javascript:sample.js
document.addEvenetListener('DOMContentLoaded', function() {
 //<aid="inner">要素のclickイベントリスナー
 document.getElementById('inner').addEventListener('click', function() {
  window.alert('#innerリスナーが発生しました')
 }, false)
 document.getElementById('inner').addEvenetListener('click', function() {
  window.alert('#innerリスナー２が発生しました')
 }, false)
 document.getElementById('outer').addEvenetListener('click', function() {
  window.alert('#outerリスナーが発生しました')
 }, false)
}, false)
```
captureがfalseに設定されていると、「イベントの発生元を基準として、上位ノードへ向かって順にイベントリスナーが実行される」ので、上記の子どを実行した場合。

1. #innerリスナーが発生しました
2. #innerリスナー２が発生しました
3. #outerリスナーが発生しました
4. リンクによってページ移動

逆にcaptureがtrueに設定されていると、「上位ノードからイベントの発生元に向かって、イベントリスナーが実行される」ので、

1. #outerリスナーが発生しました
2. #innerリスナーが発生しました
3. #innerリスナー２が発生しました
4. リンクによりページ移動

となる。

##イベントの伝搬をキャンセルする
たとえば、目的要素のイベントリスナーだけを実行して、上位ノードのイベントリスナーをキャンセルしたい場合があります。
このような場合に、stopPropagationメソッドを使います。

```javascript:sample.js
document.addEvenetListener('DOMContentLoaded', function() {
 //<a id="inner">要素のclickイベントリスナー
 document.getElementById('inner').addEventListener('click', function(e) {
  window.alert('#innerリスナーが発生しました')
  //<a id="inner">要素のイベントリスナーをキャンセルする
  e.stopPropagation()
 }, false)
 document.getElementById('inner').addEvenetListener('click', function() {
  window.alert('#innerリスナー２が発生しました')
 }, false)
 document.getElementById('outer').addEvenetListener('click', function() {
  window.alert('#outerリスナーが発生しました')
 }, false)
}, false)
```
これによって、

1. #innerリスナーが発生しました
2. #innerりすなリスナー２が発生しました
3. #リンクによってページ移動

outerイベントリスナーが実行されなくなりました。

##イベントの伝搬を直ちにキャンセルする。
stopPropagationメソッドが上位/下位要素への伝搬がキャンセルされるのに対して、stopImmediatePropagationメソッドは、その場でイベントの伝搬をキャンセルします。

```javascript:sample.js
document.addEvenetListener('DOMContentLoaded', function() {
 //<a id="inner">要素のclickイベントリスナー
 document.getElementById('inner').addEventListener('click', function(e) {
  window.alert('#innerリスナーが発生しました')
  //<a id="inner">要素のイベントリスナーを直ちに！キャンセルする
  e.stopImmediatePropagation()
 }, false)
 document.getElementById('inner').addEvenetListener('click', function() {
  window.alert('#innerリスナー２が発生しました')
 }, false)
 document.getElementById('outer').addEvenetListener('click', function() {
  window.alert('#outerリスナーが発生しました')
 }, false)
}, false)
```

こうすることで、
1. #innerリスナーが発生しました
2. リンクによるページ移動

となる。

##イベント本来の挙動をキャンセルする
イベント本来の挙動とは、「ページ移動」や「文字の反映」などのことである。

```javascript:sample.js
document.addEvenetListener('DOMContentLoaded', function() {
 //<a id="inner">要素のclickイベントリスナー
 document.getElementById('inner').addEventListener('click', function(e) {
  window.alert('#innerリスナーが発生しました')
  //<a id="inner">要素のイベントの本来の挙動をキャンセルする
  e.preventDefault()
 }, false)
 document.getElementById('inner').addEvenetListener('click', function() {
  window.alert('#innerリスナー２が発生しました')
 }, false)
 document.getElementById('outer').addEvenetListener('click', function() {
  window.alert('#outerリスナーが発生しました')
 }, false)
}, false)
```

これによって、
1. #outerリスナーが発生しました
2. #innerリスナーが発生しました
3. #innerリスナー２が発生しました

となる。これで最後にページが移動しないことが確かめられる。

つまり、stopPropagation, stopImmediatePropagation, preventDefaultをすべて呼び出すことで以降のイベントの伝搬、本来の挙動をすべて止めることができる。

#thisキーワード
イベントリスナーメソッドの配下では、thisはオブジェクト自身を指すように思えるが、イベントリスナーの配下ではthisはイベントの発生元を指している。
thisを正しく使うためにFunctionオブジェクトのbind()メソッドがある。

```javascript:sample.js
func.bind(that, [引数])
 //func : 関数オブジェクト that : 関数の中でthisキーワードが示すもの
```
たとえば、

```javascript:sample.js
document.addEvenetListener('DOMContentLoaded', function() {
 let data = {
  title: 'javascript',
  price: 2680,
  show() {
   console.log('Hello, javascript' + this.title + '/' + this.price)
  }
 }

 document.getElementById('btn', data.show.bind(data), false)
})
```
bindメソッドを使うことで、関数func配下のthisが引数に紐づけられる。

##イベントリスナーにEventListenerオブジェクトを指定する
addEventListenerメソッドの第二引数には関数を指定してきましたが、オブジェクトも指定できます。その条件は、

handleEventメソッドを持っていること

である。handleEventメソッドはbindメソッドを考慮しなくてもよいのが利点である。

```javascript:sample.js
document.addEventListener('DOMContentLoaded', function() {
 let data = {
  title: 'javascript',
  price: 2860,
  handleEvent: function() {
   console.log(this.title + '/' + this.price)
  }
 }

 document.getElementById('btn').addEventListener('click', data, false)
})
```

最低handleEventメソッドが含まれていればよいだけなので簡単である。呼び出す際もdataというオブジェクト名だけを指定するだけでよいのも簡単である。

また、bindメソッドを気にしなくてよい方法は、アロー関数を使うことである。

```javascript:sample.js
document.addEventListener('DOMContentLoaded', function() {
 let data = {
  title: 'javascript',
  price: 2860,
 }

 document.getElementById('btn').addEventListener('click', () => {
  console.log(this.title + '/' + this.price)
 }, false)
})
```
アロー関数では、thisはアロー関数自身が宣言された場所によって決まります。

#参考資料
山田祥寛様　「javascript本格入門」