formの要素を制御する。 (過去に書いたものを焼き直し)

(function () {

  function FormController (form) {
    this.form = form;
  }


  var hashTypeCheck = {// bit0: 単数or複数, bit1:input, bit2: select 
    TEXTAREA: {
      'textarea': 1
    },
    SELECT  : {
      'select-one'     : 5,
      'select-multiple': 4
    },
    INPUT   : {
      tel : 1, url : 1, email : 1,
      hidden: 1,   text : 1, search : 1, password : 1,
      date : 1, month : 1, week : 1, time : 1, datetime : 1, 'datetime-local' : 1,
      number : 1, range: 1, color : 1,
      radio: 3, checkbox : 2 
    }
  };

  
  // select 要素の option を更新する
  function replaceSelect (select, textList, valueList, selectNo, defaultNo) {
    valueList = (valueList) ? valueList: textList;
    
    var doc = select.ownerDocument;
    var option = doc.createElement ('option');
    var i, I = textList.length;
    var clone;
    
    while (i = select.firstChild) {
       select.removeChild (i);
    }
    if (selectNo instanceof Array)
      ;
    if ('number' === typeof selectNo)
      selectNo = [selectNo];
    else
      selectNo = [];

    for (i = 0; i < I; i++) {
      clone = option.cloneNode (false); // new Option
      clone.value = valueList[i];
      clone.text = textList[i];
      clone.selected = (0 <= selectNo.indexOf (i));
//      clone = new Option (textList[i],valueList[i]);
      select.appendChild (clone); //add ?
      
    }
    select.options[selectNo].selected = true;

  }

  
  // フォームのコントロールか?
  function isControls (e) {
    var hash = hashTypeCheck[e.tagName];
    var no = hash ? ('type' in e) ? (hash[e.type] || 0): 1: 0;
    return no;
  }

  // ノードリストか?
  function isNodeList (list) {
    return (list.nodeType !== 1);
  }
  
  // 配列にする
  function toArray (item) {
    return isNodeList (item) ? Array.prototype.slice.call (item, 0): [item]; 
  }

  //使われている name をすべて取得
  function getAllControllsName (form) {
    return toArray (form.elements)
      .filter (isControls)
      .reduce (normalize, []);
  }

  // 単一化
  function normalize (a, b) {
    return (-1 < a.indexOf (b.name)) ? a: a.concat (b.name);
  }
  
  // radio, checkbox の value 
  function getCheckboxValue (e) {
    return e.hasAttribute ('value') ? e.value: e.checked ? 'on': 'off';
  }
  
  // select option の value
  function getOptionValue (e) {
    return e.hasAttribute ('value') ? e.value: e.text;
  }
  
  // option を捜査し、選ばれている値を収集する
  function getSelectedValue (a, opt) {
    return (opt.selected) ? a.concat (getOptionValue (opt)): a;
  }
  // コントロールから値の取得
  function getValue (a, e, i) {
    switch (isControls(e)) {
    case 1 : case 5 : a.push (e.value); break; //5:select-one
    case 2 : case 3 : if (e.checked) a.push (getCheckboxValue (e)); break;
    case 4 : a.concat (toArray (e.options).reduce (getSelectedValue, [])); break; //selct-multiple
    }
    return a;
  }
  
  // 名前から値の取得
  function getControllValue (ctrls) {
    var result = toArray (ctrls).reduce (getValue, [ ]);
    return ctrls.length === 1 ? result[0]: result;
  }
  /*
    対象の要素を集めて、最後に .value 値を集めようと思ったが、option に無い
    場合は、.text から、checkbox には、'on' が必要だったりであきらめた。
  */
  //_______________________


  
  // 名前からオブジェクトで収集する
  function emptyObject (a, b) {
    a[b.name] = undefined;
    return a;
  }

  // 引数の変数のタイプにより、フォーム要素から値を取得して代入する
  function parser_get () {
    // arguments が無いのなら、全ての名前をオブジェクトにして再帰で呼び出す
    if (0 === arguments.length) {
      return parser_get.call (this,
        toArray (this.form.elements).filter (isControls).reduce (emptyObject, { }));
    }

    var result = [ ];
    var i, arg;
    
    for (i = 0; arg = arguments[i++];  ) {
      switch (getType (arg)) {
      case 'string' : case 'number' :
        result.push (getControllValue (this.form.elements[arg]));
        break;
      
      case 'array' :
        result.push (result.concat (arg.map (parser_get, this)));
        break;
      
      case 'object' :
        var obj = { };
        for (var name in arg)
          if (arg.hasOwnProperty (name))
            obj[name] = parser_get.call (this, name, arg[name]);
        result.push (obj);
        break;
      }
    }
    return arguments.length === 1 ? result[0]: result;
    
  }

  //_______________________
  
  // 要素が radio もしくは checkbox か?
  function isRadioChecbox (e) {
    return (e.type === 'radio') || (e.type === 'checkbox');
  }
  
  // 要素がセレクトか?
  function isSelect (e) {
    return ('SELECT' === e.tagName);
  }
  
  // 普通に e.value = xxxx で可能なもの
  function isOther (e) {
    var hash = hashTypeCheck[e.tagName];
    return hash ? ('type' in e) ? (1 === hash[e.type]): false : false;
  }
  
  // テキスト、数値 用の値設置
  function setValue (e, i) {
    var idx = this.length;
    //コントロールの数と配列の数が合わないときは、配列の最後の値を代入
    e.value = this[(idx <= i) ? idx - 1: i] || '';
  }
  
  // select-option 用の値設置
  function setOptionSelected (e) {
    e.selected = (0 <= this.indexOf (getOptionValue (e)));   
  } 
  
  // select 用の値設置
  function setValueSelect (e) {
    toArray (e.options).forEach (setOptionSelected, this);
  }
  
  // radio, checkbox 用の値設置
  function setValueRdioCheckbox (e) {
    e.checked = (0 <= this.indexOf (getCheckboxValue (e)));
  }

  function setControllValue (elements, name, value) {
    switch (getType (value)) {
    case 'string' : case 'number' : value = [value]; break;
    case 'array': break;
    default : value = [String (value)];
    }
    
    var es = elements[name];
    if (es) {
      es = toArray (es);
      es.filter (isOther).forEach (setValue, value);
      es.filter (isSelect).forEach (setValueSelect, value);
      es.filter (isRadioChecbox).forEach (setValueRdioCheckbox, value);
    }
  }


  // フォームの要素に、値をセットする。 (再帰で呼び出す)
 
  function parser_set (/* param */) {
    var len = arguments.length;
    var i = 0;
    var name, parm, type;
    
    while (i < len) {
      parm = arguments[i++];
      switch (getType (parm)) {
      case 'string' : case 'number' :
        setControllValue (this.form.elements, parm, arguments[i++]); break;
      
      case 'array' :
        parm.forEach (parser_set, this); break;
      
      case 'object' :
        for (var name in parm) {
          if (parm.hasOwnProperty (name)) {
            parser_set.call (this, name, String (parm[name]) || '');
          }
        }
        break;
      }
    }
  }
  
  function getType (o) {
    var t = typeof o;
    if ('string' === t || o === null) return 'string';
    if ('number' === t) return 'number';
    if (o instanceof Array) return 'array';
    if ('object' === t) return 'object';
  }
  
  //_______________________
  //フォームのコントロールにフォーカスが当たるとキャレットを最後尾に移動する
  function focusHandler (event) {
    if (! FormController.focusMode)
      return;
    
    var target = event.target;

    if ('INPUT' !== target.tagName) return;
    if ('text' === target.type) {
      var len = target.value.length;
      target.setSelectionRange (len, len);
      event.preventDefault ();
    }  
  }
  
  function namedItem (name) {
    return this.form.elements.namedItem (name);
  }


  // フォームのリセット
  function reset () {
    this.form.reset ();
    return this;
  }


  // オブジェクト作成
  function create (form) {
    if ((form) && ('FORM' === form.tagName))
      return new FormController (form);
    
    return null;
  }
  
  //_______________________
  document.addEventListener ('focus', focusHandler, true);

  FormController.prototype.setControllsValue = parser_set;
  FormController.prototype.getControllsValue = parser_get;
  FormController.prototype.reset = reset;
  FormController.prototype.name = namedItem;
  
  
  FormController.create              = create;
  FormController.replaceSelect       = replaceSelect;
  FormController.focusMode           = true; // フォーカスが当たるとキャレットを最後尾にする
  
  this.FormController = FormController;

  /*
   * フォームのコントロールを操作する
   * var obj = FormController.create (document.querySelector ('form'));
   
   値の取得
   object.getControlsValue (parm0, parm1);
     引数は、取得したいコントロールの名前とし、その属性にあった形で返す

     string : そのコントロールの名前の値を返す
         (その要素が複数であれば配列として返す)
     array  : 取得したい名前の配列には、その値を配列にして返す
     object : 含まれるメゾットの名前を元に、値を返す
     none   : form に含まれる全ての名前と値を、オブジェクトにして返す


   値の設定
   obj.setControllsValue (parm0, parm1, ...);
     引数は、設定したいコントロールの名前と値の順とし、その属性にあった形で
     指定できる
     
     string : そのコントロールの名前、次の引数を値として設定する
         [name0, [1,3,5,7]] select などで複数指定可の場合
         名前の要素数が設定値の配列の数より多い場合、配列の最後の値が
         以降の全てに適用される
     array  : 名前、設定値の順で配列とする。
         [name0, value0, name1, value1, ....]
         [[name0, value0], [name1, value1], [,]....]
     object : 含まれる名前を列挙し、その値を設定値とする
   
  
   コントロールにフォーカスが当たったら、キャレットを最後尾に移動する
   obj.focusMode = true; or false
   
   
   */
})();

ie は、相変わらず無視。多いにバグが含んでいる。
フォームのコントロールの値をオブジェクトで受け取ったり、設定したり。