INPUT要素のhidden属性のdefaultValueの元の値はどこにあるんだよ!?

…探せなかった。

https://stackoverflow.com/questions/2559616/javascript-true-form-reset-for-hidden-fields
確かにhidden属性を使わず、text属性にして表示させなければ問題ないか。



結局、使用する前にhidden属性のinput要素をmapを使って初期の状態を保存することにした。

{
  const
    TARGET_PARTS = ['INPUT', 'TEXTAREA', 'SELECT'],//対象となる form要素の部品
    EXCLUDE_TYPE = ['submit', 'reset', 'button', 'image', 'fieldset', 'file'],//部品から除外する


  //引数の要素が formの部品なのか?
  isParts =
    function (e) {
      return (e)
      ? (TARGET_PARTS.includes (e.tagName))
        ? ! EXCLUDE_TYPE.includes (e.type)
        : false
      : false;
    },


  //引数がノードリストなのか?
  isNodeList =
    function (arg) {
      let rst = false;

      if (arg)
        if (! ('tagName' in arg))
          if ('length' in arg)
            if ('item' in arg)
              rst = arg.item instanceof Function;

      return rst;
    },


  //要素から使用可能な要素を取り出して配列化する
  pickupParts =
    function (es) {
      return (isNodeList (es) ? Array.from (es): [es]).filter (isParts);
    },


  //引数の型を返す
  typeOf =
    function (arg) {
      return Object.prototype.toString.call (arg).slice (8, -1);
    },


  //配列の中を文字列に変換する
  insideArrayToString =
    function (ary) {
      return ary.map (v => String (v));
    },


  //OPTION@SELECTの値を取得する
  getOptionValue =
    function (opt) {
      return opt.hasAttribute ('value')
      ? opt.value
      : opt.text;
    },


  //同じ名前を持つ複数の部品がある場合並び替える
  sortByType =
    function (es) {
      let
        select_multiple = [ ],
        checkbox = [ ],
        select_one = [ ],
        radio = [ ],
        other = [ ];

      es.forEach (e => {
        switch (e.type) {
        case 'select-multiple' : select_multiple.push (e); break;
        case 'checkbox' : checkbox.push (e); break;
        case 'select-one' : select_one.push (e); break;
        case 'radio' : radio.push (e); break;
        default : other.push (e); break;
        }
      });

      return [ ].concat (select_multiple, checkbox, select_one, radio, other);
    },


  /* checkbox をセットする */
  setCheckbox =
    function (target = null, ...arg) {
      if (null === target)
        throw new Error ('要素がありません');

      let
        rst = [ ];// checked できなかったリストを返す

      arg.forEach (a => {
        let values = [ ];

        switch (typeOf (a)) {
        case 'Number' : case 'String' : values = [a]; break;
        case 'Array'                  : values = a; break;
        default                       : throw new Error ('無効な引数の型が指定されました'); break;
        }

        let
          idx = values.indexOf (target.value),
          flag = -1 < idx;

        if (target.checked = flag)
          values.splice (idx, 1);

        rst = rst.concat (values);//selected できなかった値をリスト化
      });

      return rst;
    },


  //SELCT要素の optionを設定
  setSelected =
    function (target = null, ...args) {
      if (null === target)
        throw new Error ('要素がありません');

      let
        rst = [ ],// selected できなかったリストを返す
        opts = target.options;

      args.forEach (arg => {
        let values = [ ];

        switch (typeOf (arg)) {
        case 'Number' : case 'String' : values = [arg]; break;
        case 'Array'                  : values = arg; break;
        default                       : throw new Error ('無効な引数の型が指定されました'); break;
        }

        for (let i = 0, opt; opt = opts[i]; i++) {
          if (values.length) {
            let
              val = opt.hasAttribute ('value') ? opt.value: opt.text,
              idx = values.indexOf (val),
              flag = -1 < idx;

            if ((opt.selected = flag))
              values.splice (idx, 1);
          }
        }

        rst = rst.concat (values);//selected できなかった値をリスト化
      });

      return rst;
    },



  //部品の値を返す
  value =
    function (es) {
      let
        singleFg = true,
        rst = [ ],
        i, e;

      es = pickupParts (es);
      if (! es.length)
        throw new Error ('指定された部品要素がありません');

      for (i = 0; e = es[i]; i++) {
        let type = e.type;
        if (singleFg) {
          if ('select-multiple' === type || 'checkbox' === type)
            singleFg = false;
        }

        switch (type) {
        case 'select-multiple' :
          Array.from (e.options)
            .forEach (opt => opt.selected ? rst.push (getOptionValue (opt)): null);
          break;

        case 'select-one' :
          rst.push (getOptionValue (e.options[e.selectedIndex]));
          break;

        case 'checkbox' :
        case 'radio' :
          if (e.checked)
            rst.push (e.hasAttribute ('value') ? e.value: 'on');
          break;

        default :
          rst.push (e.value);
          break;
        }
      }

      if (1 < rst.length)
        singleFg = false;

      return singleFg ? rst[0]: rst;
    },



  // formの部品に値をセットする
  setVal =
    function (es, vals) {
      es = sortByType (pickupParts (es)); //部品はソート
      if (0 === es.length)
        throw new Error ('指定された部品要素がありません');
      if (null === vals)
        vals = '';

      switch (typeOf (vals)) {
      case 'Number' : case 'String' : vals = [vals]; break;
      case 'Array'                  : break;
      default :
        throw new Error ('無効な引数の型が指定されました');
        break;
      }

      vals = insideArrayToString (vals);

      for (let i = 0, e; e = es[i]; i++) {
        switch (e.type) {

        case 'select-multiple' : case 'select-one' :
          vals = setSelected (e, vals);
          break;

        case 'checkbox' : case 'radio' :
          vals = setCheckbox (e, vals);
          break;

        default :
          e.value = value.length ? vals.shift (): '';
          break;
        }
      }
    },



  pushHidden =
    function () {
      return new Map (
        Array.from (this.form.elements)
             .filter (e => 'hidden' === e.type)
             .map (e => [e, e.defaultValue])
      );
    };


  //hidden属性の値を保存する



  //__________________________

  class ExpForm {

    /*
      引数を省略すると documentにある最初の form要素が対象となる
    */
    constructor (form = document.forms[0]) {
      if ('FORM' !== form.tagName)
        throw new Error ('FORM要素ではありません');

      this.form = form;
      this.hidden = pushHidden.call (this);

    }

    //__________________________

    /*フォームの部品の値を取得する
      引数の型により戻り値の型が変化する
      number => 部品番号の名前を元に値を返す
      string => 部品名を元に値を返す
      array  => 配列の値で指定された番号もしくは部品名で値を取得し、それらの配列順で値を返す
      object => オブジェクトのキーを元に値を取得し、オブジェクトで返す
    */
    getValue (arg = this.getEmptyObject ()) {
      let
        len = this.form.elements.length,
        e, es, name, rst;

      switch (typeOf (arg)) {


      case 'Number' :
        if (isNaN (arg))
          throw new Error ('無効な数値が指定されました');
        if (arg < 0)
          arg += len;

        e = this.form.elements[arg];//番号の要素の名前と同じ全要素を対象とする
        if (! e)
          throw new Error ('指定された部品要素がありません');
        if (! (name = e.name))
          throw new Error ('フォームの部品要素に名前がありません');

        rst = value (this.form.elements[name]);
        break;

      case 'String' :
        rst = value (this.form.elements[arg]);
        break;

      case 'Array' :
        rst = arg.map (this.getValue, this);
        break;

      case 'Object' :
        rst = Object.keys (arg).reduce ((a, b) => (a[b] = this.getValue (b), a), {});
        break;

      default :
        throw new Error ('引数の型が無効です');
      }


      return rst;
    }


    //__________________________

    /*
    form の各部品に値をセットする

    同名複数の部品がある場合 select-multiple, checkbox, その他の部品順に値を代入していく
    代入された値は、配列から削除され余ったものは捨てられる

    使い方
    書式A setValue ('aaa', 123); //=>name属性が 'aaa'の最初の要素に 123を代入する
      B setValue ('aaa', [123, 456, 789]); //=>'aaa'の複数の要素に代入する
      C setValue ({aaa: 123, bbb:[456,789]}); オブジェクト型の構造のまま代入する
    */

    setValue (arg, arg2) {
      if (! arguments.length)
        throw new Error ('引数の数が足りません');

      let
        es = this.form.elements,
        name;

      switch (typeOf (arg)) {


      case 'Number' :
        if (isNaN (arg))
          throw new Error ('無効な数値が指定されました');
        if (arg < 0)
          arg += len;

        e = es[arg];//番号の要素の名前と同じ全要素を対象とする
        if (! e)
          throw new Error ('指定された部品要素がありません');
        if (! (name = e.name))
          throw new Error ('フォームの部品要素に名前がありません');

        rst = setVal (es[name], arg2);
        break;


      case 'String' :
        setVal (es[arg], arg2);
        break;


      case 'Array' :
        rst = arg.map ((n, i) => this.setValue (n, arg2[i]));
        break;


      case 'Object' :
        Object.keys (arg).forEach (key => {
          let t;
          if (t = es[key])
            setVal (t, arg[key]);
          else
            console.log ('Worning!! 要素がありません。' + key);
        });
        break;


      default :
        throw new Error ('引数の型が無効です');
      }
    }


    //__________________________

    //form要素の部品の(重複しない)nameリストを返す
    getNames () {
      return [... new Set (Array.from (this.form.elements).filter (isParts).map (e => e.name))];
    }

    //__________________________

    //nameをキーとする空のオブジェクトを作る
    getEmptyObject (obj = { }, defVal = null) {
      return this.getNames ().reduce ((a, b) => (a[b] = defVal, a), {});
    }

    //__________________________

    //フォームをリセットする
    reset () {
      Array.from (this.form.elements).forEach (e => {
        switch (e.type) {

        case 'submit' : case 'reset' : case 'button' : case 'fieldset' : case 'image' :
          break;

        case 'radio' : case 'checkbox' :
          e.checked = e.defaultChecked;
          break;

        case 'select-one' : case 'select-multiple' :
          Array.from (e.options).forEach (o => o.selected = o.defaultSelected);
          break;

        case 'textarea' :
          e.value = e.defaultValue;
          break;

        case 'hidden' :
          let hide = this.hidden;
          e.value = hide.has (e) ? hide.get (e): '';
          break;

        default :
          e.value = e.defaultValue;
          break;
        }

      });
    }


    //__________________________

    //SELECT, DATALIST, OPTGROUP の子要素の option を配列を元に書き換える
    static replaceOptions (target, aryValue, aryText, defaultValue, selectedValue) {
      //引数確認
      if (arguments.length < 2)
        throw new Error ('引数が足りません');
      if (! /^(SELECT|DATALIST|OPTGROUP)$/.test (target.tagName))
        throw new Error ('利用できる要素ではでありません');
      if (! Array.isArray (aryValue))
        throw new Error;
      if ('undefined' === typeof aryText)
        aryText = aryValue;
      if (aryValue.length !== aryText.length)
        throw new Error ('値とテキストの配列の数が違います');

      let
        doc = target.ownerDocument,
        fgm = doc.createDocumentFragment (),
        opt = doc.createElement ('option'),
        sdv, dfv;

      switch (typeOf (defaultValue)) {
      case 'Number' : case 'String' : dfv = [defaultValue]; break;
      case 'Array'                  : dfv = defaultValue; break;
      case 'Undefined'              : dfv = false; break;
      default                       : throw new Error ('無効な引数の型が指定されました'); break;
      }

      switch (typeOf (selectedValue)) {
      case 'Number' : case 'String' : sdv = [selectedValue]; break;
      case 'Array'                  : sdv = selectedValue; break;
      case 'Undefined'              : sdv = false; break;
      default                       : throw new Error ('無効な引数の型が指定されました'); break;
      }

      //使用する配列全てを文字列化
      aryValue = insideArrayToString (aryValue);
      aryText = insideArrayToString (aryText);
      if (dfv) dfv = insideArrayToString (dfv);
      if (sdv) sdv = insideArrayToString (sdv);

      //子要素の削除
      for (let e; e = target.firstChild; )
        target.removeChild (e);

      //オプションの作成
      aryValue.forEach ((v, i) => {
        let o = opt.cloneNode (false);
        o.text = aryText[i];
        o.value = v;
        if (dfv) o.defaultSelected = dfv.include (v);
        if (sdv) o.selected = sdv.include (v);
        fgm.appendChild (o);
      })

      target.appendChild (fgm);
    }
  }

  //__________________________

  this.ExpForm = ExpForm;

//  this.isNodeList = isNodeList;
}