FORM要素の値をオブジェクト型に相互変換できるライブラリ(JSONファイルの読み込みも可)

&#65279;<!DOCTYPE hrml>
<html lang="ja">
<meta charset="UTF-8">
<style>
h1, h2, h3, h4, h5, h6, th {
  font-weight: normal;
}
h1, h2, li > em, p em { color: blue; }
section {
  column-count: 2;
  column-width: 390px;
}
section {
  padding: 0 3ex;
}
section table {
  width: 100%;
}
thead th {
  background: #def;
}
tbody:first-of-type th {
  background: #ddd;
}
tbody:first-of-type td {
  background: #eee;
}
tbody:nth-of-type(3) th {
  background: #fdd;
}
tbody:nth-of-type(3) td {
  background: #fee;
}

th {
  white-space: nowrap;
  text-align: left;
  background: #ffd;
  font-size: small;
}
td {
  background: #ffe;
  
}
td:nth-of-type(2) {
  font-size: x-small;
}

code pre {
  margin: 1ex 1ex 2em 3ex;
  padding: 1ex 1em;
  background: #def;
  border: none;
  border-left: 6px #0cf solid;
  font: normal normal small/140% monospace;
}
h3 {
  margin : 1ex 0 1ex 1em;
  border-top: 2px #09c solid;
  font-weight: normal;
  padding: 1em 0 0 0;
  line-height: 100%;
}
h4 {
  font-weight: normal;
  padding: .5ex;
  line-height: 100%;
  background: #ddf;
  display: inline-block;
  margin: 1ex 0 0 3ex;
}
p {
  margin: 1ex 1em 0.5ex 3ex;
  color: #060;
}

ul {
  margin: 0.5ex 1em 1ex 3ex;
  font-size: small;
}
</style>


<body>
<h1>ExpForm.js</h1>
<p>
  FORM 要素を対象としたライブラリです
  <input type="button" value="コードを(非)表示する" onclick="
    let e = document.querySelector ('#EXPFORM');
    e.style.display = 'block' === e.style.display ? 'none': 'block';
  ">
</p>
<div id="EXPFORM" style="display:none;">
<code><pre>
  
</pre></code>
</div>

<h3>構文</h3>
<code>
  <pre>
new ExpForm ();
new ExpForm (form);</pre>
</code>

<h3>引数</h3>
<h4>form</h4>
<p>フォーム要素を指定します。</p>

<h3>説明</h3>
<ul>
  <li>引数を与えない場合、ドキュメント上の最初のフォームが対象となります。
  <li>フォームの要素で name 属性が無いものは対象外とします
  <li>フォームの要素で type 属性が 'submit','reset','button', 'image', 'fieldset', 'file' は対象外とします
</ul>
  
<h3>インスタンス</h3>
<h4>ExpForm.prototype.reset ()</h4>
<p>form 要素の値をリセットします</p>
<ul>
  <li>hidden 属性は、ExpForm を宣言した時の状態を保存しその値を代入します
</ul>

<h4>ExpForm.prototype.get ()</h4>
<p>form 要素の値を object 型にして返します</p>
<ul>
  <li>type 属性が 'checkbox', 'select-multiple' は選択状態の要素がなくても空の配列を返します
  <li>name 属性が複数あると配列にして返します
  <li>type 属性が 'checkbox', 'radio' の選択状態が全くない場合は null を返します
</ul>

<h4>ExpForm.prototype.set (object)</h4>
<p>object 型を走査して form の要素に値を設定します</p>
<ul>
  <li>name 属性が同じで複数ある場合は type 属性を以下の順でソートしてから設定します<br>
      <em>select-multiple -> checkbox -> select-one -> radio -> その他</em>
  <li>type 属性が 'select-one', 'radio' の要素に、配列から複数の値を設定しようとした場合、<br>
      複数の選択項目の最後にマッチした value 値の要素を選択状態にします
  <li>type 属性が以下のもので選択条件にマッチする要素は、その全てが選択状態となります<br>
      また、それ以外の要素には利用されなかった配列の value 値から順に設定されます<br>
      <em>"select-multiple", "checkbox", "select-one", "radio"</em>
  <li>list 属性があるものは、その option の value 値に含まれるものがあれば、優先して設定されます
</ul>

<h4>ExpForm.prototype.setFromFile (file_name)</h4>
<p>ファイルを読み込んで、要素に値を設定します</p>
<ul>
  <li>読み込むファイルは JSON 形式である必要があります。
  <li>その後は objec 型に変換し toFromObject () の引数として呼び出します
  <li>JSONファイルのキーとなる要素が無い場合は、無視されます
</ul>

<h4>ExpForm.prototype.replaceOptions (object)</h4>
<p>object 型を走査して option 要素を置き換えます</p>
<ul>
  <li>SELECT 要素もしくは list 属性で示された要素(datalist) がその対象となります
  <li>option 要素は、一旦削除されてから上書きされます。
  <li>object の構造は、例として以下のようになります<br>
  <code><pre>
{
  "name": [
    { "value": "abc" },
    { "value": "def", "text": "DEF" },
    { "value": "ghi", "text": "GHI", "defaultSelewcted": true, "selected": true },
    { "gropu": "japan", "text": "いろは", "value": "イロハ" },
  ],

  "name2": [
    { "value": "abc" },
    { "value": "def", "text": "DEF" }
  ]
}</pre></code>
  
</ul>


<h4>ExpForm.prototype.replaceOptionsFromFile (file_name)</h4>
<p>ファイルを読み込んで、option 要素を置き換えます</p>
<ul>
  <li>読み込むファイルは JSON 形式である必要があります。
  <li>その後は objec 型に変換し replaceOptions () の引数として呼び出します
  <li>ファイル名に "./test.php?arg=1" の用にクエリーを付けて呼び出し<br>
      PHP 側で$_GET['arg'] などで取得し、ファイルの出力を変える方法もありえます
</ul>



<h3>静的メゾット</h3>
<p>引数に使われる elements はノードリストの他に object 形式でも利用可能です</p>
<code><pre>
let elements = {
  'hoge_select-one': [
    {group: '春', value: 'かつお', text: '鰹'},
    {group: '春', value: 'たこ', text: '蛸'}
  ],
  'hoge_select-multiple': [
    {group: '夏', value: 'あじ', text: '鯵'},
    {group: '夏', value: 'あゆ', text: '鮎'}
  ]
};

</pre></code>

<h4>ExpForm.get (elements)</h4>
<p>elements 要素の値を object 型にして返します</p>
<ul>
  <li>引数の違いだけで動作は、インスタンスの ExpForm.prototype.get に準じます
</ul>

<h4>ExpForm.set (elements, object)</h4>
<p>object 型を走査して elements の要素に値を設定します</p>
<ul>
  <li>引数の違いだけで動作は、インスタンスの ExpForm.prototype.set に準じます
</ul>

<h4>ExpForm.setFromFile (elements, file_name)</h4>
<p>ファイル(JSON)を読み込んで、要素に値を設定します</p>
<ul>
  <li>引数の違いだけで動作は、インスタンスの ExpForm.prototype.setFromFile に準じます
</ul>

<h4>ExpForm.replaceOptions (elements, object)</h4>
<p>object 型を走査して option 要素を置き換えます</p>
<ul>
  <li>引数の違いだけで動作は、インスタンスの ExpForm.prototype.replaceOptions に準じます
</ul>

<h4>ExpForm.fileLoader (file_name)</h4>
<p>ファイル(JSON)を読み込んで object 型にして返します</p>



<h3 id="TEST">テスト用のフォーム</h3>
<section id="cmp">
  <form id="hoge">
    <table border="1">
      <caption>form#hoge の要素</caption>
      <thead>
        <tr>
          <th>type
          <th>element
      <tbody>
        <tr>
          <th>type="file"
          <td><input name="hoge_file" type="file" value="sample.txt">
        <tr>
          <th>type="submit"
          <td><input name="hoge_submit" type="submit" value="Submit">
        <tr>
          <th>type="image"
          <td><input name="hoge_image" type="image" value="" alt="ボタンの画像">
        <tr>
          <th>type="reset"
          <td><input name="hoge_reset" type="reset" value="Reset">
        <tr>
          <th>type="button"
          <td><input name="hoge_button" type="button" value="button">
      <tbody>
        <tr>
          <th>type="hidden"
          <td onclick="alert(this.firstChild.value);"><input name="hoge_hidden" type="hidden" value="hide">
          clickで変数値をアラート

        <tr>
          <th>type="text"
          <td><input name="hoge_text" type="text" value="これはテキスト">
        <tr>
          <th>type="search"
          <td><input name="hoge_search" type="search" value="検索文字">
        <tr>
          <th>type="tel"
          <td><input name="hoge_tel" type="tel" value="0120-12-3456">
        <tr>
          <th>type="url"
          <td><input name="hoge_url" type="url" value="http://raspberry.ne.jp">
        <tr>
          <th>type="email"
          <td><input name="hoge_email" type="email" value="hoge@raspberry.ne.jp">
        <tr>
          <th>type="password"
          <td><input name="hoge_password" type="password" value="1234567890">
        <tr>
          <th>type="datetime"
          <td><input name="hoge_datetime" type="datetime" value="2020-08-19T12:34:56.789">
        <tr>
          <th>type="date"
          <td><input name="hoge_date" type="date" value="2021-01-01">
        <tr>
          <th>type="month"
          <td><input name="hoge_month" type="month" value="2020-02">
        <tr>
          <th>type="week"
          <td><input name="hoge_week" type="week" value="2020-W30">
        <tr>
          <th>type="time"
          <td><input name="hoge_time" type="time" value="12:34:56">
        <tr>
          <th>type="datetime-local"
          <td><input name="hoge_datetime-local" type="datetime-local" value="2020-08-19T12:34:56.789">
        <tr>
          <th>type="number"
          <td><input name="hoge_number" type="number" value="123">
        <tr>
          <th>type="range"
          <td><input name="hoge_range" type="range" value="1000">
        <tr>
          <th>type="color"
          <td><input name="hoge_color" type="color" value="#000000">
        <tr>
          <th>type="checkbox"
          <td><input name="hoge_checkbox" type="checkbox" value="abc">abc
              <input name="hoge_checkbox" type="checkbox" value="def">def
              <input name="hoge_checkbox" type="checkbox" value="ghi">ghi
        <tr>
          <th>type="radio"
          <td><input name="hoge_radio" type="radio" value="abc">abc
              <input name="hoge_radio" type="radio" value="def">def
              <input name="hoge_radio" type="radio" value="ghi">ghi
        <tr>
          <th>type="select-one"
          <td><select name="hoge_select-one">
                <option value="">選択してください
                <option value="abc" selected>abc
                <option value="def">def
                <option value="ghi">ghi
              </select>
        <tr>
          <th>type="select-multiple"
          <td><select name="hoge_select-multiple" multiple>
                <option value="">選択してください
                <option value="abc" selected>abc
                <option value="def">def
                <option value="ghi" selected>ghi
              </select>
        <tr>
          <th>type="textarea"
          <td><textarea name="hoge_textarea" cols="20" rows="4">Sample text</textarea>

      <tbody>
        <tr>
          <th>name 属性が同じ場合
          <td>
              <select name="hoge_all" multiple>
                <option value="">選択してください
                <option value="abc">abc
                <option value="def">def
                <option value="ghi">ghi
              </select><br>
              <select name="hoge_all">
                <option value="">選択してください
                <option value="abc">abc
                <option value="def">def
                <option value="ghi">ghi
              </select><br>
              <input name="hoge_all" type="checkbox" value="abc" checked>abc
              <input name="hoge_all" type="checkbox" value="def">def
              <input name="hoge_all" type="checkbox" value="ghi" checked>ghi<br>
              <input name="hoge_all" type="radio" value="abc">abc
              <input name="hoge_all" type="radio" value="def" checked>def
              <input name="hoge_all" type="radio" value="ghi">ghi<br>
              <textarea name="hoge_all" cols="20" rows="4">Sample text</textarea><br>
              <input type="text" name="hoge_all" value="123456789">

              
              
    </table>
    
  </form>


  <form id="hoge2">
    <table border="1">
      <caption>form#hoge2 の要素</caption>
      <thead>
        <tr>
          <th>type
          <th>element
      <tbody>
        <tr>
          <th>type="file"
          <td><input name="hoge_file" type="file" value="">
        <tr>
          <th>type="submit"
          <td><input name="hoge_submit" type="submit" value="Submit">
        <tr>
          <th>type="image"
          <td><input name="hoge_image" type="image" value="" alt="ボタンの画像">
        <tr>
          <th>type="reset"
          <td><input name="hoge_reset" type="reset" value="Reset">
        <tr>
          <th>type="button"
          <td><input name="hoge_button" type="button" value="button">
      <tbody>

        <tr>
          <th>type="hidden"
          <td onclick="alert(this.firstChild.value);"><input name="hoge_hidden" type="hidden" value="">
          clickで変数値をアラート
        <tr>
          <th>type="text"
          <td><input name="hoge_text" type="text" value="" list="NO_NULL">
        <tr>
          <th>type="search"
          <td><input name="hoge_search" type="search" value="">
        <tr>
          <th>type="tel"
          <td><input name="hoge_tel" type="tel" value="">
        <tr>
          <th>type="url"
          <td><input name="hoge_url" type="url" value="">
        <tr>
          <th>type="email"
          <td><input name="hoge_email" type="email" value="">
        <tr>
          <th>type="password"
          <td><input name="hoge_password" type="password" value="">
        <tr>
          <th>type="datetime"
          <td><input name="hoge_datetime" type="datetime" value="">
        <tr>
          <th>type="date"
          <td><input name="hoge_date" type="date" value="">
        <tr>
          <th>type="month"
          <td><input name="hoge_month" type="month" value="">
        <tr>
          <th>type="week"
          <td><input name="hoge_week" type="week" value="">
        <tr>
          <th>type="time"
          <td><input name="hoge_time" type="time" value="">
        <tr>
          <th>type="datetime-local"
          <td><input name="hoge_datetime-local" type="datetime-local" value="">
        <tr>
          <th>type="number"
          <td><input name="hoge_number" type="number" value="">
        <tr>
          <th>type="range"
          <td><input name="hoge_range" type="range" value="">
        <tr>
          <th>type="color"
          <td><input name="hoge_color" type="color" value="">
        <tr>
          <th>type="checkbox"
          <td><input name="hoge_checkbox" type="checkbox" value="abc">abc
              <input name="hoge_checkbox" type="checkbox" value="def">def
              <input name="hoge_checkbox" type="checkbox" value="ghi">ghi
        <tr>
          <th>type="radio"
          <td><input name="hoge_radio" type="radio" value="abc">abc
              <input name="hoge_radio" type="radio" value="def">def
              <input name="hoge_radio" type="radio" value="ghi">ghi
        <tr>
          <th>type="select-one"
          <td><select name="hoge_select-one">
                <option value="">選択してください
                <option value="abc">abc
                <option value="def">def
                <option value="ghi">ghi
              </select>
        <tr>
          <th>type="select-multiple"
          <td><select name="hoge_select-multiple" multiple>
                <option value="">選択してください
                <option value="abc">abc
                <option value="def">def
                <option value="ghi">ghi
              </select>
        <tr>
          <th>type="textarea"
          <td><textarea name="hoge_textarea" cols="20" rows="4"></textarea>

      <tbody>
        <tr>
          <th>name 属性が同じ場合<br>
              (並び順に注意)
          <td>
              <input type="text" name="hoge_all" value="" list="NO"><br>
              <textarea name="hoge_all" cols="20" rows="4"></textarea><br>
              <input name="hoge_all" type="radio" value="abc">abc
              <input name="hoge_all" type="radio" value="def">def
              <input name="hoge_all" type="radio" value="ghi">ghi<br>
              <input name="hoge_all" type="checkbox" value="abc">abc
              <input name="hoge_all" type="checkbox" value="def">def
              <input name="hoge_all" type="checkbox" value="ghi">ghi<br>
              <select name="hoge_all">
                <option value="">選択してください
                <option value="abc">abc
                <option value="def">def
                <option value="ghi">ghi
              </select><br>
              <select name="hoge_all" multiple>
                <option value="">選択してください
                <option value="abc">abc
                <option value="def">def
                <option value="ghi">ghi
              </select><br>
          
    </table>
    <datalist id="NO">
      <option value="123">
      <option value="123456789">
      <option value="abcdefg">
    </datalist>
  </form>
</section>



<h3>使用例</h3>
<h4>例:フォームから他のフォームにコピーする</h4>
<code><pre>
let
  hoge = new ExpForm (document.querySelector ('form#hoge')),
  hoge2 = new ExpForm (document.querySelector ('form#hoge2')),
  hoge_obj = hoge.get ();

hoge2.set (hoge_obj);
</pre></code>
<p>
  <input type="button" value="実行する" onclick="
let
  hoge = new ExpForm (document.querySelector ('form#hoge')),
  hoge2 = new ExpForm (document.querySelector ('form#hoge2')),
  hoge_obj = hoge.get ();

hoge2.set (hoge_obj);
">
</p>


<h4>例2:フォームのコピー</h4>
<code><pre>
let
  hoge = document.querySelector ('form#hoge'),
  hoge2 = document.querySelector ('form#hoge2');
  
ExpForm.set (hoge2.elements, ExpForm.get (hoge.elements));
</pre></code>
<p>
  <input type="button" value="実行する" onclick="
let
  hoge = document.querySelector ('form#hoge'),
  hoge2 = document.querySelector ('form#hoge2');
  
ExpForm.set (hoge2.elements, ExpForm.get (hoge.elements));
">
</p>


<h4>例:フォームの指定されたname属性の値を取得コピーする</h4>
<code><pre>
let
  hoge = new ExpForm (document.querySelector ('form#hoge')),
  hoge2 = new ExpForm (document.querySelector ('form#hoge2'));

let
  rec = hoge.get ('hoge_text', 'hoge_hidden', 'hoge_all');
hoge2.set (rec);
</pre></code>
<p>
  <input type="button" value="実行する" onclick="
let
  hoge = new ExpForm (document.querySelector ('form#hoge')),
  hoge2 = new ExpForm (document.querySelector ('form#hoge2'));

let
  rec = hoge.get ('hoge_text', 'hoge_hidden', 'hoge_all');
hoge2.set (rec);
">
</p>




<h4>例:ファイル(JSON)から読み込んでフォームに値を設定する</h4>
<code><pre>
let exform = new ExpForm (document.forms[0]);
exform.setFromFile ('./test.json');
</pre></code>



<h4>例:SELECT 要素の option の値を書き換える</h4>
<code><pre>
let selects = {
  'hoge_select-one': [
    {value: '', text: '選択してください', selected: true},
    {group: '春', value: 'うめ', text: '梅'},
    {group: '春', value: 'さくら', text: '桜'},
    {group: '夏', value: 'あさがお', text: '朝顔'},
    {group: '夏', value: 'ひまわり', text: '向日葵'},
    {group: '秋', value: 'すすき', text: '芒'},
    {group: '冬', value: 'すいせん', text: '水仙'},
    {value: 'ざっそう', text: '雑草'}
  ],
  'hoge_select-multiple': [
    {value: '', text: '選択してください'},
    {group: '春', value: 'うめ', text: '梅', selected: true},
    {group: '春', value: 'さくら', text: '桜', selected: true},
    {group: '夏', value: 'あさがお', text: '朝顔'},
    {group: '夏', value: 'ひまわり', text: '向日葵'},
    {group: '秋', value: 'すすき', text: '芒'},
    {group: '冬', value: 'すいせん', text: '水仙'},
    {value: 'ざっそう', text: '雑草'}
  ]
};
ExpForm. replaceOptions (document.querySelector ('form#hoge2').elements, selects);
</pre></code>


<p>
  <input type="button" value="実行する" onclick="
let selects = {
  'hoge_select-one': [
    {value: '', text: '選択してください', selected: true},
    {group: '春', value: 'うめ', text: '梅'},
    {group: '春', value: 'さくら', text: '桜'},
    {group: '夏', value: 'あさがお', text: '朝顔'},
    {group: '夏', value: 'ひまわり', text: '向日葵'},
    {group: '秋', value: 'すすき', text: '芒'},
    {group: '冬', value: 'すいせん', text: '水仙'},
    {value: 'ざっそう', text: '雑草'}
  ],
  'hoge_select-multiple': [
    {value: '', text: '選択してください'},
    {group: '春', value: 'うめ', text: '梅', selected: true},
    {group: '春', value: 'さくら', text: '桜', selected: true},
    {group: '夏', value: 'あさがお', text: '朝顔'},
    {group: '夏', value: 'ひまわり', text: '向日葵'},
    {group: '秋', value: 'すすき', text: '芒'},
    {group: '冬', value: 'すいせん', text: '水仙'},
    {value: 'ざっそう', text: '雑草'}
  ]
};
ExpForm. replaceOptions (document.querySelector ('form#hoge2').elements, selects);
  ">


<h4>例:異なる FORM の中の 複数の SELECT 要素の option の値を書き換える</h4>
<code><pre>
let
  es = {
    "hoge_select-one": document.querySelector ('#hoge select[name="hoge_select-one"]'),
    "hoge_select-multiple": document.querySelector ('#hoge2 select[name="hoge_select-multiple"]'),
  };

let selects = {
  'hoge_select-one': [
    {group: '春', value: 'かつお', text: '鰹'},
    {group: '春', value: 'たこ', text: '蛸'}
  ],
  'hoge_select-multiple': [
    {group: '夏', value: 'あじ', text: '鯵'},
    {group: '夏', value: 'あゆ', text: '鮎'}
  ]
};

ExpForm.replaceOptions (es, selects);
</pre></code>
<p>
  <input type="button" value="実行する" onclick="
let
  es = {
    'hoge_select-one': document.querySelector ('#hoge select[name=hoge_select-one]'),
    'hoge_select-multiple': document.querySelector ('#hoge2 select[name=hoge_select-multiple]'),
  };

let selects = {
  'hoge_select-one': [
    {group: '春', value: 'かつお', text: '鰹'},
    {group: '春', value: 'たこ', text: '蛸'}
  ],
  'hoge_select-multiple': [
    {group: '夏', value: 'あじ', text: '鯵'},
    {group: '夏', value: 'あゆ', text: '鮎'}
  ]
};
ExpForm.replaceOptions (es, selects);

"></p>

<script>
{
  const
    //要素のソートの基準
    PRIORITY_ELEMENTS = {
      'select-multiple': 10,
      'checkbox': 9,
      'select-one': 8,
      'radio': 7,
    },
 
    //要素のソートのコールバック関数
    priority = ({type:a}, {type:b}) =>
      ((PRIORITY_ELEMENTS[a] || 0) < (PRIORITY_ELEMENTS[b] || 0)),

    //input type="hidden" の値を格納する
    pushHiddenValue = (form) =>
      new Map (
        Array.from (form.elements)
             .filter (e => 'hidden' === e.type)
             .map (e => [e, e.defaultValue])
        ),

    //hidden属性の値を初期状態に設定する (thisを使うので通常のfunction)
    popHiddenValue = obj =>
      Array.from (obj.form.elements)
           .filter (e => 'hidden' === e.type)
           .forEach (e => e.value = obj.hidden.get (e)),

    //子要素のノードの全削除
    removeAllChild = p => {
      while (p.hasChildNodes ())
        p.removeChild (p.firstChild);
    },


    //ファイルからの読み込み
    fileLoader = (file) => {
      if (! XMLHttpRequest)
        throw new Error ('利用できません');

      let
        req = new XMLHttpRequest (),
        res = null;
        
      req.open ('GET', file, false);
      req.send (null);
      res = req.responseText.replace(/(\u2028|\u2029|\s|\r\n|\r|\n)/gm,'');
      
      try {
        return JSON.parse (res);
      } catch (err) {
        console.log (res);
        throw new Error ('JSONに変換できませんでした');
      }
    },

    
    //要素に設定する
    setValue = (es = null, obj = { }) => {
      if (null === es)
        throw new Error ('FORMの中の要素がありません');

      for (let name of Object.keys (obj)) {// !hasOwnProperty
        let target = es[name];
        if (! target)
          continue;

        //ELEMENT_NODE以外はノードリストとして扱う
        if (target.nodeType === Node.ELEMENT_NODE)
          target = [target];
        
        let values = obj[name];
        if (! Array.isArray (values))
          values = [values];
    
        let used   = Array.of.apply ([ ], values);//value の配列で利用された値を保存
        
        target = Array.from (target).sort (priority);
        
        EScanning: //jump label
        for (let e of target) {
          switch (e.type) {
          case 'submit' : case 'reset' : case 'button' :
          case 'image'  : case 'fieldset': case 'file' :
            continue; break; //pass
    
          case 'select-one' :
          case 'select-multiple' : {
            let op = e.options;
            if (op.length)
              for (let o of e.options) {
                if (values.includes (o.value)) {
                  o.selected = true;
                  let idx = used.indexOf (o.value);
                  if (-1 < idx)
                    used.splice (idx, 1);
                }
              }
            }
            break;
              
          case 'radio' :
          case 'checkbox' :
            let checked = e.checked = values.includes (e.value);
            if (checked) {
              let idx = used.indexOf (e.value);
              if (-1 < idx)
                used.splice (idx, 1);
            }
            break;
              
          default:
            //datalist にoption値があれば、それを優先する
            if (e.hasAttribute ('list')) {
              let op = e.list ? e.list.options: null;
              if (op) {
                for (let v of Array.from (op, o => o.value)) {
                  let x = used.indexOf (String (v));
                  if (-1 < x) {
                    e.value = used.splice (x, 1);
                    continue EScanning;
                  }
                }
              }
            }

            e.value = used.shift () || '';
            break;
          }
        }
      }
    },
    
    
    
    //
    getValue = (es) => {
      let
        result = { };
      
      for (let e of es) {
        let type = e.type, name;
    
        switch (type) {//列挙したものはパス
        case 'submit' : case 'reset' : case 'button' :
        case 'image'  : case 'fieldset': case 'file' :
          continue; break; //pass
        default :
          name = e.name;
          if (! name) continue;//name を持たないものもパス
        }
    
        let value =  result[name], v;
        //一度でもnameが存在するものは null値で初期化
        if ('undefined' === typeof value)
          result[name] = value = null;
    
        switch (type) {
        case 'select-multiple' :
          //v = Array.from (e.options).filter (o => o.selected).map (o => o.value) || [];
          //v = Array.from (e.options).reduce ((a, b) => (a.selected ? b.concat (a): b), [])
          v = [];
          if (-1 < e.selectedIndex)
            for (let o of e.options)
              if (o.selected)
                v.push (o.value);
          break;
    
        case 'select-one' :
          if (0 > e.selectedIndex) continue;
          v = e.options[e.selectedIndex].value;
          break;
    
        case 'checkbox' :
          v = e.checked ? [e.value]: [ ];
          break;
    
        case 'radio' :
          if (! e.checked) continue; //else "そのまま defaultを利用する"
        default :
          v = e.value;
        }
        
        result[name] =
          (Array.isArray (value))
          ? value.concat (v) // 結合して配列に
          : (null !== value)
            ? [value, v] // すでに値があるのだから配列化
            : v;
      }
    
      return result;
    },
    
    
    
    //
    replaceOptions = (elements, obj) => {
      Object.keys (obj).forEach (name => {
        let es = elements[name];
        
        es = es
             ? es.nodeType === Node.ELEMENT_NODE
               ? [es]
               : Array.from (es)
             : [ ];

        for (let e of es) {
          let rec = obj[name], dl;
          if ('SELECT' === e.tagName) {
            let
              m = null,
              current = null,
              obj;

            removeAllChild (e);

            while (obj = rec.shift ()) {
              let
                label = obj.group || null,
                option = new Option (
                  ('undefined' !== typeof obj.text ? obj.text: obj.value),
                  obj.value,
                  !! obj.defaultSelected,
                  !! obj.selected
                );

              if (m !== label) {
                let optgroup = e.ownerDocument.createElement ('optgroup');
                optgroup.setAttribute ('label', label);
                current = label ? e.appendChild (optgroup): null;
              }
              
              (current || e).appendChild (option);
              m = label;
            }
          
          } else {
            if (e.hasAttribute ('list')) {
              if (dl = e.list) {
                removeAllChild (dl);
                for (let r of rec) {
                  dl.appendChild (new Option (r.text, r.value));
                }
              }
            }
          }
        }
      })
    },
    
    
    //pickup
    O = (a,b) => Array.isArray (b) ? b.reduce (O, a): (a.push (b), a),//一次元化
    U = (a,b) => a.has (b) ? a: a.set (b, 1), //単一化
    S = a => a ? 'string' === typeof a : false, //文字列化
    L = (a,b) => b.nodeType === Node.ELEMENT_NODE ? (a.push (b), a): a.concat ([...b]),
    N = a => !!a,
    
    pickup = function (elements, ...args) {
      return [...
        [...args//引数を
           .reduce (O, [ ])//一次配列化して
           .reduce (U, new Map)//マップに登録
           .keys ()//単一化したものを取り出す
        ].filter (S)//文字列だけ
         .map (n => elements[n])//FORMの中の要素を取り出し
         .filter (N)//要素があるものだけ
         .reduce (L, [])//配列に集める
         .reduce (U, new Map)//マップに登録
         .keys()//単一化したものを取り出す
        ];    
    };


  //#######################


  class ExpForm {
    //定義
    constructor (form = document.form[0]) {
      if (1 > arguments.length)
        throw new Erorr ('引数が足りません');
      if ('FORM' !== form.tagName)
        throw new Erorr ('FORMではありません');
       
      this.form = form;
      this.hidden = pushHiddenValue (form);
    }
    
    
    //リセット
    reset () {
      this.form.reset ();
      popHiddenValue (this);
      return this;
    }
    
    
    //オブジェクトの値をフォームにセットする
    set (obj = { }) {
      setValue (this.form.elements, obj);
      return this;
    }
    
    
    //フォームの値をオブジェクトにする
    get (...args) {
      let
        es = this.form.elements,
        elements = 0 === args.length
        ? es
        : pickup (es, args);

      return getValue (elements);
    }
    
  
  
    //ファイルからの読み込みフォームに値をセットする
    setFromFile (file) {
      if (1 > arguments.length)
        throw new Error ('ファイル名が指定されていません');
   
      this.set (fileLoader (file));
      return this;
    }
    
    

    //option を書き換えます
    replaceOptions (obj) {
      if (1 > arguments.length)
        throw new Error ('引数が足りません');
      replaceOptions (this.form.elements, obj);
      return this;
    }


    
    //ファイルから読み込んで option を置き換えます
    replaceOptionsFromFile (file) {
      if (1 > arguments.length)
        throw new Error ('ファイル名が指定されていません');
      let json = fileLoader (file);
      return this.replaceOptions (json);
    }

    //#######################
    
    //
    static set (elements, obj) {
      setValue (elements, obj);
    }
    
    static setFromFile (elements, file) {
      setValue (elements, fileLoader (file));
    }
    
    static get (es, ...args) {
      let
        elements = 0 === args.length
        ? es
        : pickup (es, args);

      return getValue (es);
    }
    
    static replaceOptions (elements, obj) {
      replaceOptions (elements, obj);
    }
  
    static replaceOptionsFromFile (elements, file) {
      let json = fileLoader (file);
      replaceOptions (elements, json);
    }
    
    static fileLoader (file) {
      return fileLoader (file);
    }
    
    static pickup (es, args) {
      return pickup (es, args);
    }
  
  }

  //_____________________________
  
  this.ExpForm = ExpForm;

}

</script>

<script>
document.querySelector ('#EXPFORM code pre').textContent =
  document.querySelector ('script').textContent;
</script>