JavaScriptとWebアプリケーション開発　後半

### 筆者について
JavaScriptを勉強し始めて、1-2日目。(Java-Gold取得済み)

### 記事の内容
「[改訂新版]JavaScript 本格入門」 Chapter6 「HTMLやXMLの文書を操作する ​－ DOM ( Document Object Model ) －」(山田祥寛, 出版 : 技術評論社) 以降を読みながら、メモをしたものなので、内容の誤記、誤字等に関しては、ご容赦いただいたうえで、ご指摘いただけると幸いである。またやんわりとHTMLなどに関する知識がないと難解な内容に感じるかもしれない。ご容赦。
[これ](https://qiita.com/4023/private/312f752fd0c0726594c6)のつづき。



## フォーム要素へのアクセス

フォームはエンドユーザーから入力を受けとる代表的な手段。入力ボックスや、チェックボックス、ラジオボタンなどいろいろある。

### 入力ボックス・選択ボックスの値を取得

```html
<form>
 <label> for="name">名前:</label>
 <input id="name" name="name" type="text" size="30" />
 <input id="btn" type="button" value="送信" />
</form>
<div id="result"></div>
```
```Javascript
document.addEventListener('DOMContentLoaded', function(){
    document.getElementById('btn').addEventListener('click', function(){
        var name = document.getElementById('name');
        console.log(name.value);
    }, false;
}, false);
```

ボタンをクリックすると、フォーム値をログに列挙する。

### チェックボックスの値の取得・設定

入力ボックスの値の取得より複雑で、id属性が異なるが、
name属性が共通のチェックボックスの値の取得には、
getElementsByNameメソッドを使うのが良い。

```html
<form>
    <div>
        好きな食べ物は？
        <label>
            <input type="checkbox" name="food" value="ピーマン" />
            ピーマン
        </label>
        <label>
            <input type="checkbox" name="food" value="きゅうり" />
            きゅうり
        </label>
        <label>
            <input type="checkbox" name="food" value="サンマ" />
            サンマ
        </label>
        <label>
            <input type="checkbox" name="food" value="寒天" />
            寒天
        </label>
        <input id="btn" type="button" value="送信" />
    </div>
</form>
```
```Javascript
document.addEventListener('DOMContentLoaded', function () {
    document.getElementsById('btn').addEventListener('click', function () {
        var result = [];
        var foods = document.getElementsByName('food');

        for (var i = 0, len = foods.length; i < len, i++) {
            var food = foods.item(i);
            if (food.checked) {
                result.push(food.value);
            }
        }
        window.alert(result.toString());
    }, false);
}, false);
```
valueプロパティは選択の有無にかかわらず、value属性で指定された値を返すため、選択状況の確認には使えない。

ラジオボタンに関しても同様の処理で値を取得することができる。
一方でラジオボタンは選択肢の一つしかチェックをいれることができないため、選択済み要素が見つかり次第、for文から抜ける処理を書くのがベターだろう。

ラジオボタン/チェックボックスにチェックを入れるには、同じような繰り返し処理で、指定したvalue値と等しい要素の***checked***プロパティをtrueにすれば良い。

これらの処理方法と、それぞれのフォームのプロパティ値を合わせて、応用することができる。

```Javascript
//複数選択できるリストボックスの選択肢を返す
elem.options;
//チェックボックス・ラジオボタンの選択状況
elem.items(i).checked //Bool
//ファイル
file.name //ファイル名
file.type //コンテンツタイプ
file.size //サイズ(byte単位)
file.lastModifiedDate //最終更新日
```

画像ファイル、テキストファイル、バイナリファイルなどに加え、データをデータベースに保存することもできる。

## ノードを追加、置換、削除

DOMでは既存ノードの参照のみならず、新規ノードの作成などもできる。
先にinnerHTMLについて挙げたが、これは
コンテンツが複雑になったときに、可読性が下がる可能性がある。
ユーザーからの入力によりコンテンツを作成する場合に、脆弱性となる恐れがある。

シンプルなコンテンツの編集でない限りは避けたほうが良いかもしれない。

### 新規ノードの作成

```html
<form>
    <div>
        <label for="name">サイト名</label><br />
        <input id="name" name="name" type="text" size="30"/>
    </div>
    <div>
        <label for="url">URL : </label><br />
        <input id="url" name="url" type="url" size="50" />
    </div>
    <div>
        <input id="btn" type="button" value="追加"　/>
    </div>
</form>
<div id="list"></div>
```

```Javascript
document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('btn').addEventListener('click', function () {
        var name = document.getElementById('name');
        var url = document.getElementById('url');
        // <a>要素の生成
        var anchor = document.createElement('a');
        // <a>要素のhref属性の設定
        anchor.href = url.value;
        // テキストノードを生成し、<a>直下に追加
        var text = document.createTextNode(name, value);
        anchor.appendChild(text);
        // <br>要素の生成
        var br = document.createElement('br');
        // <div id="list">を取得
        var list = document.getElementById('list');
        // <div>要素直下に<a>、<br>要素を順番で追加
        list.appendChild(anchor);
        list.appendChild(br);
    }, false);
}, false);
```


フォームに入力した値に従って、ページ下部にリンクを追加するコード。

#### 要素などの生成

createXxxxxメソッドは以下のようなものがあり、指定したノードを返す。

```Javascript
createElement(elem); //要素ノード
createAttribute(attr); // 属性ノード
createTextNode(text); // テキストノード
createCDATASection(text); // CDATAノード
createComment(text); // コメントノード
createEntityReference(enti); // 実体参照ノード
createProcessingInstruction(target, data); // 処理命令ノード
createDocumentFragment(); // ドキュメントの断片
```
※テキストノードに<>/などのような文字を入れると、自動的にエスケープされる。
#### ノードを組み立てる

```Javascript
elem.appendChild(node)
elem.insertBefore(node1, node2)
```

先に上げたコードでは、生成した、< a > 要素の属性を設定してから、指定した要素の下に子ノードとして追加している。
insertBeforeメソッドは、第一引数に指定したノードを第二引数で指定したノードの直前に挿入する。第二引数をnullにした場合、ノードを最後尾に挿入する。

コンテンツを追加する際には、再描画がを少なくするように処理をさせる。
再描画はオーバーヘッドが高い処理のため、発生させる回数は少なくする。
そのために
・DocumentFragmentオブジェクト上でコンテンツを組み立て
・まとめて文書ツリーに追加
という手順を踏む。

```http
<ul id="list"></ul>
```
```Javascript
document.addEventListener('DOMContentLoaded', function () {
    var books = [
        { title: 'Sample', price: 1980 },
        { title: 'Beispiel', price: 2300 },
        { title: 'Rei', price: 6890 }
    ];
    var frag = document.createDocumentFragment();
    //fragmentDocument上で組み立てて
    for (var i = 0, len = books.length; i < len; i++) {
        var b = books[i];
        var li = document.createElement('li');
        var text = document.createTextNode(b.title + ':' + b.price + '円');
        li.appendChild(text);
        frag.appendChild(li);
    }
    //まとめて文書ツリーに追加
    document.getElementById('list').appendChild(frag);
}, false);
```

####既存のノードを置換・削除
```
//置換
elem.replaceChild(after, before)
elem:要素オブジェクト　after:置き換え後のノード　before:置き換え対象のノード

//削除
elem.removeChild(node)
node:削除対象ノード

//属性の削除
elem.removeAttribute(attr);
attr:属性名
```

### 繰り返し処理をする際の注意
HTMLCollection、NodeListは現在の状態を常に参照するため、

```
for(var i = 0; i < li.length; i++){
    //<li>要素を追加する処理
    ...
}
```
のような処理があった場合、`i < li.length`がfalseになることがなくなり、無限ループになる。

`len = li.length ; i < len`のように初期化式で、現在の値を格納した変数を用意することが良い。
また基本的に、lengthプロパティの参照はオーバーヘッドが大きいため、常に先に上げたような、条件式が望ましいだろう。

## スタイルシートの操作

DOMからスタイルシートにアクセスするのは、
・インラインスタイルへのアクセス
・外部のスタイルシートを適用する
の二つがある。

### styleプロパティ

個々に適用されたインラインスタイルにアクセスする。

```
elem.style.prop[ = value]
elem:要素オブジェクト　prop:スタイルプロパティ　value:設定値
```

```html
<div id="elem">マウスポインターを乗せると色が変わる</div>
```
```javascript
document.addEventListener('DOMContentLoeded', funtion(){
    var elem = document.getElementById('elem');

    //ポインタが載ったときに背景色を変更
    elem.addEventListener('mouseover', function() {
        this.style.backgroundColor = 'YELLOW';
    }, false);
    //ポインタが離れたら背景色を戻す
    elem.addEventListener('mouseout', function() {
        this.style.backgroundColor = '';
    }, false);
}, false);
```
JavaScript内でスタイルプロパティ名を指定するときは、html内で「－」を含んでいたものは、それを取り除き、２単語目以降の頭文字を大文字にして書き換える必要がある。

### classNameプロパティ

styleプロパティは簡単に記述できる一方で、スタイルの定義とスクリプトが混在しているので、
可読性が低く、保守しづらい。変更が出たときに、変更すべき箇所がわかりにくい。
スクリプト側では、スタイルの関連付けを切り替える程度でとどめることが望ましい。

外部スタイルシートで定義されたスタイルにアクセス

```
elem.className [= clazz]
elem:要素オブジェクト　clazz:スタイルクラス
```

```html:style.html
<link rel="stylesheet" href="css/style.css" />
...
<div id="elem">マウスを乗せると色が変化</div>
```
```css:style.css
.highlight {
  background-color: YELLOW;
}
```
```javascript:style.js
document.addEventListener('DOMContentLoaded', function () {
    var elem = document.getElementById('elem');

    elem.addEventListener('mouseover', function () {
        this.className = 'highlight';
    })

    elem.addEventListener('mouseout', function () {
        this.className = '';
    }, false);
}, false);
```
以上のようにスクリプトでは、class属性の変更のみを行っている。また複数のクラスの関連付けの場合は

```javascript
this.className = 'clazz1 clazz2';
```
のように、半角スペースで区切って列挙することで可能となる。

条件演算子「?」を使ってスタイルクラスの着脱もできる。

```javascript
this.className = (this.className === 'highlight' ? '' : 'highlight');
```

### classListプロパティ

classListプロパティを参照することで、class属性の値を***DOMTokenListオブジェクト***として取得できる。DOMTokenListのメンバは以下のものがある。

```
length　リストの長さ
item(idex)　インテックスの番目のクラス取得
contains(clazz)　指定したクラスが含まれているか
add(clazz)　リストにクラスを追加
remove(clazz)　リストからクラスを削除
toggle(clazz)　クラスのオン/オフの切り替え
```

```html
<link rel="stylesheet" href="css/style.css" />
...
<div id="elem" class="line">クリックすると色が変化</div>
```
```javascript
document.addEventListener('DOMContentLoaded', function () {
    var elem = document.getElementById('elem');

    elem.addEventListener('click', function () {
        this.classList.toggle('highlight');
    }, false);
}, false);
```
このように簡潔にわかりやすく記述することができた。

## 高度なイベント処理

### イベントリスナ・イベントハンドラの削除

イベントハンドラの削除は
`onxxxx = null` 
のように、イベントハンドラにnull値を設定することで削除できる。

イベントリスナの削除

```
elem.removeEventListener(type, listener, capture)
elem:要素オブジェクト type:イベントの種類　listener:削除するイベントリスナ capture:イベント伝播方向
```

削除するには、イベントリスナを指定しなければならないため、イベントリスナ作成時に匿名クラスで作成すると、指定することができず、削除することができない。そのため、あとから参照できるように関数オブジェクトとして格納しておく必要がある。

### イベントオブジェクト

イベントハンドラ、イベントリスナは引数としてイベントオブジェクトを受けとることができる。
このイベントオブジェクトがもつプロパティにアクセスすることで、様々な情報にアクセスすることができる。

イベントオブジェクトにはイベント発生時の情報がまとめて格納される。
（ポインタ座標、イベント種類、押されたマウスボタン、キーボードの入力情報など）

```javascript
document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('btn').addEventListener('click', function (e) {
        //target イベント発生元の要素
        var target = e.target;
        console.log(target.nodeName + target.id);
        //type イベントの種類
        console.log(e.type);
    }, false)
}, false)
```

イベントオブジェクトを表す引数は一般的に「e」「ev」。
利用しない場合は、省略することもできる。

マウスポインタの位置を取得する場合に、
スクリーン上、ページ上、ブラウザの表示領域、要素内
の各それぞれの左端を原点に右、下の方向に向かっての座標を取得する。

```
screenX/screenY スクリーン上
pageX/pageY　ページ上
clientX/clientY　表示領域上
offsetX/offsetY　要素領域上
```

### イベント処理のキャンセル

#### イベントの伝播について

イベントが特定の要素に達するまでには
・キャプチャフェーズ
・ターゲットフェーズ
・バブリングフェーズ
のような手順を踏んでいる。

キャプチャフェーズでは最上位のwindowオブジェクトから文書ツリーをたどって、下位要素にイベントが伝播していく。
ターゲットフェイズでイベントとの発生元の要素を特定する。
バブリングフェーズでは、イベントの発生元から、ルート要素をたどって、イベントが伝播していく。
最終的に最上位のwindowオブジェクトに伝播していき、終了する。

イベントリスナ生成時の第３引数を
`false`にすると、バブリングフェーズ時に、イベントが処理される。
→下位要素からイベントが実行。
`true` にすると、キャプチャフェーズ時に、イベントが処理される。
→上位要素からイベントが実行。

#### イベントの伝播をキャンセル

`e.stopPropagation()`メソッドによって、下位、上位要素へのイベントの伝播を中止する。
同じ階層のオブジェクトのイベントは実行される。バブリング、キャプチャが中止される。

`e.stopImmediatePropagation()`メソッドは、その場で直ちに電波を中止する。
同じ要素に登録されたリスナがあっても、それも実行されない。

#### イベント本来の挙動をキャンセル

`e.preventDefault()`メソッドを行うと、本来挙動(リンク先への移動など)が実行されなくなる。
これは、イベントオブジェクトの`cancelable`プロパティがtrueの場合に可能であり、そうでない場合は、キャンセルすることができない。


### イベントハンドラ、イベントリスナ下のthisキーワード

それぞれの下にある、thisキーワードは常に、
イベントの発生元を表す。
→メソッドの呼び出し先にthisキーワードが使用されていた場合、想定していた挙動とは違う挙動をする恐れがある。

そこで、`bind`オブジェクトを利用する

```
func.bind(that [,arg1 [, arg2[, ...]]])
func:関数オブジェクト that:関数の中でthisキーワードが表すもの
arg1, ... :関数に渡す引数
```

```javascript
document.addEventLitener('DOMContentListener', data.show, false);
```
というところを

```javascript
document.addEventLitener('DOMContentListener', data.show.bind(data), false);
```
とすることで、showメソッドにあるであろう、thisキーワードが表す変数を、dataに指定することができた。

### イベントリスナにEventListenerオブジェクトを指定

`addEventListener`メソッドの第二引数には、`function`オブジェクトを指定してきたが、
***handleEvenrメソッドをもったオブジェクト***を指定することもできる。
オブジェクトを指定することで、thisキーワードを持ったメソッドに対して、bindメソッドを用いることなく、想定した挙動を導くことができる。

```javascript
document.addEventListener('DOMContentLoaded', function () {
    var data = {
        title: 'sample',
        price: 1980,
        handleEvent: function () {
            console.log(this.title + this.price);
        }
    };

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