フォーム要素にモダンにアクセスするイディオム・定石


## **目標**
- **for** ループを使わないこと。
- html側の変更に強いこと。
- できるだけモダンに。
- 可読性を高くしたい。

## **テキストボックスのテキストを取得・設定する**

![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/d9f7eb14-f29c-abe1-87bf-af3fa2d37bab.png)

```html
    <form>
      <input type="text" id="field">
      <p id="message"></p>
    </form>
```

```JavaScript
document.addEventListener('DOMContentLoaded', () =>{
  const textbox = document.getElementById('field');
  const message = document.getElementById('message');
  textbox.value ='なにか入力してください';    // ※１

  textbox.addEventListener('keyup', () => {
    message.textContent = textbox.value;    // ※２
  }, false);
}, false);
```

テキストボックスのテキストは取得した **input** 要素の **value** プロパティに代入すると表示できます。（※１）
入力されたテキストも **value** プロパティから取得できます。（※２）
**email**, **search**, **tel**, **number**, **range**, **time**, **date**, **color**, **datetime-local**, **month**, **week**, **textarea** も同じです。

#### **参考**

- [document.getElementById - MDN web docs](https://developer.mozilla.org/ja/docs/Web/API/Document/getElementById)
- [input 要素 - MDN web docs](https://developer.mozilla.org/ja/docs/Web/HTML/Element/Input)


## **単一セレクトボックスの値を取得、設定する**

![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/2e3925f0-66bc-1022-c6be-809934102161.png)

```html:selectbox.html
    <form>
      <select id="sel">
        <option value="value1">Value 1</option>
        <option value="value2">Value 2</option>
        <option value="value3">Value 3</option>
      </select>
      <input type="button" id="btn" value="送信">
    </form>
```

```JavaScript:selectbox.js
window.addEventListener('DOMContentLoaded', () => {
  const select = document.getElementById('sel');
  select.value = 'value2';

  select.addEventListener('change', (e) => {    // ※１
    console.log(e.target.value);
  }, false);
}, false);
```

セレクトボックスの選択が変わった時、コンソールにその値を出力します。
**```event.target```** からイベントを発生させたオブジェクトへの参照を取得します。（※１）
**```.getElementById()```** でセレクト要素取得し **value** プロパティを参照しても同じです。

#### **参考**

- [Event.target - MDN web docs](https://developer.mozilla.org/ja/docs/Web/API/Event/target)

## **複数選択できるリストボックスの選択された値の一覧を取得する。**

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/195024/030f8b6d-bf2a-819c-374a-431026ca7ed9.png)

```html:listbox.html
    <form>
      <label for="menu">ご注文は？：</label>
      <select id="menu" multiple>
        <option value="ramen">ラーメン</option>
        <option value="curry">カレー</option>
        <option value="tonkatsu">とんかつ</option>
        <option value="karaage">から揚げ定食</option>
      </select>
      <input type="button" id="btn" value="注文">
    </form>
```

### **getElementXxxx と Array.prototype.filter()**

```JavaScript:listbox1.js
window.addEventListener('DOMContentLoaded', () => {
  const getSelectedValues = id => {

    // id で select 要素を取得しその配下の HTMLOptionCollection を得る
    const options = document.getElementById(id).options;
    
    // セレクトされたオプションの値の配列を返す
    return Array.from(options)      // 配列に変換して ※１
      .filter(el => el.selected)    // 選択された要素のみの配列を作り
      .map(el => el.value);         // 配列のvalueをマッピングする
  }

  const button = document.getElementById('btn');
  button.addEventListener('click', () => {
    const values = getSelectedValues('menu');

    console.log(values);    // ex. ["curry","tonkatsu"]
  }, false);
}, false);
```

セレクタ式と違い文字列ではないので **IDE** の入力補完を受けられます。（特に TypeScript など静的型付けのAltJSで書くと顕著）
『配列のようなオブジェクト』から配列に変換する方法は以下のようなものもあります。（※１）

```JavaScript
// arrayLikeObj ← 配列のようなオブジェクト（ NodeList や arguments 等）

array = [...arrayLikeObj]    //スプレッド演算子で展開したものを配列に格納する
array = [].map.call(arrayLikeObj, el => el)    // 要素を取り出し配列としてマッピング（あまり見ない）
array = [].slice.call(arrayLikeObj)     // レガシー？？
```

 Arrayの反復メソッドを使うと別の条件も簡単付けられます。

```JavaScript
// 選択されている、かつ、クラス名に foo を含む要素を抽出する
[...elems].filter(el => el.selected && el.classList.contains('foo'));
```

また、**call メソッド**で **this** に **HTMLCollction** や **NodeList** を渡すと最初に配列へ変換せずに **Array** の反復メソッドを使用できます。

```JavaScript
    return [].filter.call(elems, el =>  el.checked)
             .map(el => el.value);
```

#### **参考**

- [Array - MDN web docs](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)
- [スプレッド演算子 - MDN web docs](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_operator)



### **querySelectorAll()**

```JavaScript:listbox2.js
window.addEventListener('DOMContentLoaded', () => {
  const getSelectedvalues = id => {

    // select 要素配下の選択された option 要素を NodeList として得る
    const checkedOptions = document.querySelectorAll(`#${id} option:checked`); // ※１
    return [...checkedOptions].map(opt => opt.value);
  }

  document.getElementById('btn').addEventListener('click', () => {
    const values = getSelectedvalues('menu');

    console.log(values);    // ex. ["curry","karaage"]
  }, false);
}, false);
```

テンプレートリテラルを使って **ID** を埋め込みます。
セレクター式の場合 **option** 要素の選択状態を表すのは **selected** ではなく **checked** であることに注意します。（※１）

### **参考**

- [【Javascript】querySelector、querySelectorAll（CSSセレクタで要素取得）](https://www.softel.co.jp/blogs/tech/archives/2085)
- [document.querySelectorAll - MDN web docs](https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll)
- [テンプレート文字列 - MDN web docs](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/template_strings)

## **複数選択できるリストボックスの選択状態を設定する**

```JavaScript:set_listbox.js
window.addEventListener('DOMContentLoaded', () => {
  const setListbox = (id, ...values) => {    // ※１
    const select = document.getElementById(id);    // セレクト要素を取得
    Array.from(select.options)    // セレクト要素配下のオプション要素を配列に変換する
      .forEach(opt => opt.selected = values.includes(opt.value)); 
  };
  setListbox('menu', 'ramen', 'karaage');
}, false);
```

スプレッド演算子を使って可変長引数を定義します。（※1）
関数の利用者は**```...```** で一目で引数が可変長とわかります。
スプレッド演算子を使わない場合でも **arguments** から引数にアクセスできます。
しかし、**arguments** オブジェクトは配列ではないので配列に変換しなければ配列の反復メソッドは使えません。
スプレッド演算子で宣言された可変長引数は **Array** であるのでそのまま **```.includes()```** などを使用できます。


## **チェックされたチェックボックスの値を取得する**

![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/879ce6e7-1356-e2a5-0dcf-95b2288a9d9b.png)

```html:checkbox.html
    <form>
      <div id="books">
        <label>
          <input type="checkbox" value="Don Quijote de la Mancha" name="book">
          ドン・キホーテ
        </label>
        <label>
          <input type="checkbox" value="The Little Prince" name="book">
          星の王子様
        </label>
        <label>
          <input type="checkbox" value="The Catcher in the Rye" name="book">
          ライ麦畑で捕まえて
        </label>
      </div>
      <input type="button" id="btn" value="送信">
    </form>
```

### **Array.prototype.filter()**

```JavaScript:checkbox1.js
window.addEventListener('DOMContentLoaded', () => {
  const getCheckedValues = name => {

    // HTMLOptionElement の NodeList を得る
    const elems = document.getElementsByName(name);

    // チェックされたinput要素の値の配列を返す
    return [...elems].filter(el => el.checked)
      .map(el => el.value);
  }

  document.getElementById('btn').addEventListener('click', () => {
    const values = getCheckedValues('book');
    console.log(values);    // ex. ["Don Quijote de la Mancha","The Catcher in the Rye"]
  }, false);
}, false);
```

選択状態を表すプロパティが **selected** でなく **checked** である以外、特にリストボックスとの違いはありません。

### **.querySelctorAll()**

```JavaScript:checkbox2.js
window.addEventListener('DOMContentLoaded', () => {
  const getCheckedValues = id => {
    const checked = document.querySelectorAll(`#${id} input[type="checkbox"]:checked`);
    return [...checked].map(el => el.value);
  }

  document.getElementById('btn').addEventListener('click', () => {
    const values = getCheckedValues('books');

    console.log(values);    // ex. ["Don Quijote de la Mancha","The Catcher in the Rye"]
  }, false);
}, false);
```

## **チェックボックスの選択状態を設定する**

```JavaScript:set_checkbox.js
const setCheckBox = (name, ...values) => {
  const elems = document.getElementsByName(name);
  elems.forEach(el => el.checked = values.includes(el.value));    // ※１
}

window.addEventListener('DOMContentLoaded', () => {
  setCheckBox('book', 'Don Quijote de la Mancha', 'The Catcher in the Rye');
}, false);
```

それぞれの **input** 要素の **value** が設定する**values**に含まれている場合、チェックします。（※1）
**getElementsByName()** の戻り値は **NodeList** で配列ではありませんが **.forEach()** があります。
（**HTMLCollction** には **```.forEach()```** はありません。）

## **ラジオボタンの値を取得する**

![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/3f9ca07a-600a-3980-a1f7-d0d0cf09502e.png)

```html:radio.html
    <form>
      <!-- ラジオボタンは name 属性で一組となります。 -->
      <label>
        <input type="radio" value="vue" name="framework">
        Vue.js
      </label>
      <label>
        <input type="radio" value="angular" name="framework">
        Angular4
      </label>
      <label>
        <input type="radio" value="react" name="framework">
        React.js
      </label>
      <input type="button" id="btn" value="送信">
    </form>
```

### **getElementXxxx と Array.prototype.find()**

```JavaScript:radio.js
window.addEventListener('DOMContentLoaded', () => {
  const getCheckedRadioValue = name => {
    const elems = document.getElementsByName(name);
    const checkedButton = [...elems].find(el => el.checked);    // ※1
    return checkedButton ? checkedButton.value : '';
  };
  document.getElementById('btn').addEventListener('click', () => {
    const value = getCheckedRadioValue('framework');

    console.log(value);    // ex. angular
  }, false);
}, false);
```

ラジオボタンは一つしか選択されないため最初に条件に合致（checkedプロパティがtrue）した要素を **.find()** で取得しその **value** 返します。（※1）
ラジオボタンが選択されていない場合、三項演算子で空文字を返します。

## **ラジオボタンの選択状態を設定する**

```JavaScript:set_radio.js
const setRadioButton = (name, value) => {
  const radios = document.getElementsByName(name);
  [...radios].some(el => el.checked = (el.value === value));    // ※１
};

window.addEventListener('DOMContentLoaded', () => {
  setRadioButton('framework', 'vue');
}, false);
```

ラジオボタンは **name** 属性でグループ化される単一選択です。
ループを回してラジオボタンをチェックする際、**```.some()```** によりチェック直後にループを脱出します。（※1）
この **```.some()```** の使い方は本来のものではないという考えもあります。
**```.some()```** を使わない場合、無駄なループを回すことになりますが **```.forEach()```** を使う、もしくは **```for of```** でチェックをしたら **break** でループを脱出します。

#### **参考**

- [[JavaScript] ループ途中で抜ける処理は for(in break) ではなく some を使おう♪](http://qiita.com/phi/items/f998763d2d52bdd57a7c)



ここまで読んでいただきありがとうございました。
**Array** の反復メソッドの詳細はすべて **[Array - MDN web docs](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)** の反復メソッドの項に載っています。