【Dot install】Document Object Model (DOM)基礎編まとめ

# 目的

Dot installで詳解されているDocument Object Motel (DOM)の内容をまとめる。
//ProgateだとDOMは扱われなかった。

# 対象

- DOM基礎を学びたい方
- Dot installを一周し、復習をしたい方

# 詳解JavaScript DOM編

## HTMLの基礎 ~DOMとは~

1. HTMLを読み込む
2. DOMと呼ばれるデータ構造が作成される
3. その内容に応じてページが描画される
   1. Javascriptで操作するのは、htmlではなく、DOM。

## Nodeとは

  - DOM内の1つ1つのデータを**Node**と呼ぶ。
  - Documentから始まり、ツリー状に枝分かれ＝**Node Tree (DOM Tree)**

### Nodeの種類
  
  - HTMLの要素を表すNode：**Element Node (要素Node)**
    - eg. `<head>`, `<title>`
  - Text、改行、空白：**Text Node**  
    - 改行、空白もNode。
      - ただしhtml要素の先頭と末尾の改行、空白はNodeにならない。

### Node間の関係

- 親子・兄弟といった単語を頻用する。
  - eg. `head`に対する`html`: Parent Node
  - eg.2 `head`に対する`title`: Chiled Node
  - eg.3 `</head>`後の改行、空白と`<body>`: Sibling Node

## DOMの操作

- DOMはdocumentオブジェクトで操作。

### 要素の取得

- `querySelector(狙う要素(Selector))`: 文書内から特定のSelectorを取得する。
- 要素を取得する際にcssクラスまたはid属性で指定する。
  - id属性を指定する際には要素名の前に`#`をつける。
  - **見つかった要素のうち、最初の1つしか取得しない。**

- `querySelectorAll(狙う要素(Selector))[i]`: 文書内の指定したSelectorすべてを対象とする。`i`には要素のindex（何番目か？）を指定する。
  - オブジェクトなので、`.forEach()`が使用可。
  - 他にも
    - `getElementByTagName('h1')`: 文書内の指定したTagすべてを取得
    - `getElementByClassName('box')`: 文書内の指定したclassすべてを取得

- `getElementById`: id属性を取得する。
  - 要素名の前に`#`は不要。

- `.chileNode`: 要素の child nodeをすべて取得する。
  - `.children`: child nodesのうちelement nodesのみ取得する。
  - `.firstChild`: 最初のchild noteを取得
  - `.lastChild`: 最後のchild noteを取得
  - `.firstElementChild`: 最初のchild note (element node)を取得
  - `.lastElementChild`: 最後のchild note (element node)を取得
- `.parentNode`: 要素の parent nodeを取得する。
- `.previousSibling`: 要素の前のsibling nodeを取得する。
  - `.previousElementSibling`: 要素の前のsibling node (element node) を取得する。
- `.nextSibling`: 要素の後のsibling nodeを取得する。
  - `.nextElementSibling`: 要素の後のsibling node (element node) を取得する。

#### コードサンプル

- `querySelector(狙う要素)`および`getElementById`
```html: index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Javascript DOM Practice</title>
  </head>
  <body>
    <h1 id="target">hogehoge</h1>
    <p>fugafuga</p>
    <p>fugafuga</p>
    <p>fugafuga</p>
    <script src="js/main.js"></script>
  </body>
</html>
```

```javascript: main.js
{
  function update() {
    document.querySelector('#target').textContent = "Changed!";
  }

  setTimeout(update, 1000);
}
```

または

```javascript: main.js
{
  function update() {
    document.getElementById('target').textContent = "Changed!";
  }

  setTimeout(update, 1000);
}
```

- `querySelectorAll(狙う要素)`

```javascript: main.js
{
  function update() {
    document.querySelectorAll('p').forEach((p, index) => {
      p.textContent = `${index}番目のpです！`;
    });
  }
  setTimeout(update, 1000);
}
```

### イベント

- `.addEventListener(動作, 関数)`: 指定したnodeについて、ある`動作`が起きた際に`関数`で指定した処理をする。

```javascript: main.js

// button要素をclickした際に、target idのテキストをChanged!に変更する。

document.querySelector("button").addEventListener("click", () => {
    document.getElementById("target").textContent = "Changed!";
}); 
```

### 要素の操作

- `h1`タグの`title`や`color`, `background-color`要素を操作する。


```javascript: main.js
document.querySelector("button").addEventListener("click", () => {
  const targetNode = document.getElementById("target");

  targetNode.textContent = "Changed!";
  targetNode.title = "This is my title!";
  targetNode.style.color = "red";
  targetNode.style.backgroundColor = "skyblue";
});
```

:::note info
補足

CSSでは`background-color`と指定していたが、javascriptでは`backgroundColor`のように、**ｰを消して2単語目を大文字にする。**

※ただし、一般的に見た目の指定は上記のように直接行うのではなく、classを付与する形で行う。

:::

### クラスの操作

- `.className = ""`: classを`""`内に書き換え

:::note warn
注意！

もともとclassが指定されている要素の場合、classNameで付与したクラスに完全に書き換わる。
:::

- `.classList.add("")`: ""のclassを新規追加。
- `.classList.remove("")`:""のclassを消去。
- `.classList.contains("")`: ""のclassの有無を確認。結果はbooleanで返される。

```javascript: main.js

document.querySelector("button").addEventListener("click", () => {
  const targetNode = document.getElementById("target");

  //"my-color"classが付与されていたら消去し、付与されていなかったら追加する。

  if (targetNode.classList.contains('my-color') === true) {
    targetNode.classList.remove("my-color"); 
  } else {
    targetNode.classList.add("my-color");
  }
  
});


```

- 上記と同様の挙動は`.classList.toggle()`で記載可能。

```javascript: main.js

document.querySelector("button").addEventListener("click", () => {
  const targetNode = document.getElementById("target");

  //"my-color"classが付与されていたら消去し、付与されていなかったら追加する。
  targetNode.classList.toggle("my-color");  
});
```

### カスタムデータ属性の操作

- `.dataset.(カスタムデータ属性)`
  htmnl, jsにデータが分散していると後から編集しづらい。そこで、htmlにまとめてデータ属性を付与することで後からの編集をしやすくする。

```html: index.html
<body>
  <button>Run!</button>
  <h1 id="target" data-translation="HOGEHOGE">hogehoge</h1>
  <p>fugafuga</p>
  <p>fugafuga</p>
  <p>fugafuga</p>
  <script src="js/main.js">
  </script>
</body>
```

```javascript: main.js
  document.querySelector("button").addEventListener("click", () => {
    const targetNode = document.getElementById("target");

    //h1タグのtextをtranslationで指定した内容に変更する。
    targetNode.textContent = targetNode.dataset.translation;
  });
```

### 要素の追加

- createElement('追加したい要素'): 追加したい要素を生成。
  あとは、追加したい要素をquerySelector等で指定すればok.

```javascript
  document.querySelector("button").addEventListener("click", () => {
    const item2 = document.createElement('li'); //li要素を生成
    item2.textContent = "fuga 2"; //li要素のtextを指定

    const ol = document.querySelector('ol'); 
    ol.appendChild(item2); //ol要素の下に追加

  });
```

### 要素の複製と追加

- `.cloneNode(boolean)`: 指定したノードの複製。()内trueでcontentまで複製。
- `.insertBefore(挿入したいnode, 挿入する場所(このnodeの前に挿入される))`: 指定したnodeの前にnodeを追加する。


```javascript
document.querySelector("button").addEventListener("click", () => {
    const item0 = document.querySelectorAll("li")[0];
    const copy = item0.cloneNode(true); //item0のnodeを複製してcopy nodeを生成。falseの場合はitem0のtextが複製されない

    const ol = document.querySelector('ol');

    const item2 = document.querySelectorAll('li')[2];
    ol.insertBefore(copy, item2); //item2の前にcopy nodeを追加。
});

```
### 要素の削除

- `.remove('削除したいnode')`: 一部のブラウザで対応不可。
- `.removeChild('削除したいnodeの親node')`: 汎用的に使える方法

```javascript

{
  document.querySelector("button").addEventListener("click", () => {
    const item1 = document.querySelectorAll('li')[0];


    item1.remove(); //item1 nodeを削除。ただし一部のブラウザでは対応不可。よって書き方も覚えておく。
    document.querySelector('ol').removeChild(item1); 

  });
}


```

### input要素, option要素, ラジオボタンの操作

#### input要素

- `input`に入力された内容 => `node名.value`で取得可能。
  → `textContent`に`node名.value`で取得した値を代入する等すれば、`input`で受け取った値を表示することが可能になる。


```html: index.html
<!-- headは省略 -->

<body>
  <input type="text">
  <button>Add</button>

  <ul>
  </ul>

  <script src="js/main.js">
  </script>
</body>

```

```javascript: main.js
{
  document.querySelector("button").addEventListener("click", () => {
    const li = document.createElement("li"); //li要素を追加。
    const text = document.querySelector("input");
    li.textContent = text.value; //inputフォームに記載された値(value)を取得してli nodeのtextとして代入。
    document.querySelector('ul').appendChild(li);

    text.value = "";
    text.focus();

  });
}
```

#### option要素

- `select`に対しても、`value`で値を取得できる。

```html: index.html
<body>
  <select>
    <option>赤</option>
    <option>黄</option>
    <option>青</option>
  </select>

  <button>Add</button>

  <ul>
  </ul>

  <script src="js/main.js">
  </script>
</body>

```

```javascript: main.js
{
  document.querySelector("button").addEventListener("click", () => {
    const li = document.createElement("li");
    const color = document.querySelector("select"); //select内にぶらさがっているchild nodeがすべて取得される。
    li.textContent = `${color.value} - ${color.selectedIndex}`;

    document.querySelector('ul').appendChild(li);

    color.focus(); 
  });
}

```

#### ラジオボタン

- ラジオボタンが複数ある場合、`input`要素をすべて取得する必要があるため、`select`要素とは異なる点に注意。

```html: index.html
<body>
  <input type="radio" name="color" value="red"> 赤
  <input type="radio" name="color" value="yellow"> 黄
  <input type="radio" name="color" value="blue"> 青

  <button>Add</button>

  <ul>
  </ul>

  <script src="js/main.js">
  </script>
</body>

```

```javascript
{
  document.querySelector("button").addEventListener("click", () => {
    const colors = document.querySelectorAll("input"); //ここですべてのラジオボタンを取得。
    
    let selectedColor; //選択したボタン（色）を入れる変数を準備

    colors.forEach(color => {
      if (color.checked === true) {
        selectedColor = color.value; //ラジオボタン.checkedがtrueとなっている要素の値をselectedColor変数に代入
      }
    });

    const li = document.createElement("li");
    li.textContent = selectedColor;
    document.querySelector('ul').appendChild(li);
  });
}

```

#### チェックボックス

- チェックボックスは複数選択可で、チェックした要素を**配列**で扱うことに注意。

```html: index.html
<body>
  <input type="checkbox" value="red"> 赤
  <input type="checkbox" value="yellow"> 黄
  <input type="checkbox" value="blue"> 青

  <button>Add</button>

  <ul>
  </ul>

  <script src="js/main.js">
  </script>
</body>
```

```javascript: main.js
{
  document.querySelector("button").addEventListener("click", () => {
    const colors = document.querySelectorAll("input");
    const selectedColors = []; //チェックボックスの場合、複数選択可。要素を配列で保持しておく。配列の内容は変わるが、再代入はされないため、constで宣言する。

    colors.forEach(color => {
      if (color.checked === true) {
        selectedColors.push(color.value); //pushでtrueだったcolorのvalue追加
      }
    });

    const li = document.createElement("li");
    li.textContent = selectedColors.join(","); //, 区切りで値を代入
    document.querySelector("ul").appendChild(li);


  });
}
```

:::note info

- 配列への要素の追加: `.push()`
- 配列を文字列として代入: `.join('区切り文字')`

:::


### 様々なイベント

- `dblclick`: ダブルクリックしたとき
- `mousemove`: マウスを動かしたとき
- `keydown`: キーを押したとき
  - ここで引数を渡すと、マウスの座標・押されたキー等取得できる。
  - ここで指定する引数をevent objectと呼び、慣習的に`e`で指定される。

```javascript

'use strict';

{
  document.querySelector("button").addEventListener("dblclick", () => {
    console.log("Double click!");
  });

  document.addEventListener("mousemove", e => { //ここで引数を渡せば、ブラウザからイベントからの情報を渡してくれる
    //ここでの引数は、event objectと呼ばれ、慣習的にeが用いられる。
    console.log("Mouse moved!");
    console.log(e.clientX, e.clientY);
  });

  document.addEventListener("keydown", e => {
    console.log(e.key);
  });

}

```

### textarea関係

- `focus`: textareaにフォーカスされたとき
- `blur`: textareaからフォーカスが外れたとき
- `input`: textareaに文字が入力されたとき
- `change`: textareaに入力された文字が確定されたとき

```javascript

'use strict';

{
  const text = document.querySelector("textarea");

  text.addEventListener("focus", () => { //focusあたった際のイベント
    console.log("focus.");
  })

  text.addEventListener("blur", () => { //focus外れた際のイベント
    console.log("blur.");
  })

  text.addEventListener("input", () => { //文字が入力された際のイベント
    console.log("input.");
    console.log(text.value.length); //入力された文字カウント
  })

  text.addEventListener("change", () => { //入力された文字が確定した際のイベント
    console.log("change.");
  })


}

```

### form

- `form`については、`submit`がイベントとなる。
  - `form`をもちいることのメリット：エンターキーで`form`内のテキストが送信される。
  - 通常は`submit`するとページがリロードされる。`e.preventDefault`によって、リロードする通常動作をキャンセルできる。
  - フォームをエンターキーで送信するには、`form`タグの中に`button`タグが必要だが、`form`タグ中に`<input type=”text”>`が1つだけだった場合は`button`を省略してもエンターキーだけで送信できる。

```javascript
'use strict';

{
  document.querySelector("form").addEventListener("submit", e => {
    e.preventDefault(); //リロードする通常動作をキャンセルする。
    console.log("Submit!");
  });
}

```

### イベントの伝播

- `addEventListener`の引数で`target`とすれば、`child node`すべてがイベントの対象となる。

```html: index.html
<body>
  <ul> <!-- e.currentTarget -->
    <li>To do</li>
    <li>To do</li> <!-- e.target -->
    <li>To do</li>
  </ul>

  <script src="js/main.js">
  </script>
</body>
```

```javascript: main.js
{
  document.querySelector("ul").addEventListener("click", e => {
    if (e.target.nodeName === "LI") { //nodeNameは大文字
      e.target.classList.toggle("done");
    }
  });

}


```

以上。