FORM 要素の部品の要素の値を object型にして返す
必要になって書いてみた。プログラム書くのに時間がかかりすぎ。
使い方のメモ
let f0 = new ExpForm (document.forms[0]), f1 = new ExpForm (document.forms[1]), obj = f0.getValue (); //form0からform1に複写 f1.setValue (obj);
- FORM要素の対象となる部品は、INPUT, TEXTAREA, SELECT とする。
- type属性が['submit', 'reset', 'button', 'image', 'fieldset', 'file']のものは除外する
- setValue()時に、もし name属性が同じ部品で、type属性が数種類あった場合には、以下の優先順に並び替えて指定値を代入する
(プログラム的には、その指定値(配列)のバッファから設定するたびに削除しながらループしている)
select-multiple > checkbox > select-one > radio > その他
- getValue()の時に type属性が select-multiple, checkbox が含まれる場合、必ず配列にして返す
- null での設定は、空白文字("")に置き換える
追加修正
- setValue() を実行する時 checkbox の設定が追加上書きされていたので、追加されないように修正(新規に設定)。
- setValue() を実行する時、同名の部品の数より、設定しようとする値の配列の数が少ない場合、途中で終了していたので最後までループするように修正。その場合は、空文字を代入することにした
サンプルとして同じフォームを2つ作りボタンでコピーしてみる
<!DOCTYPE hrml> <head> <meta charset="utf-8"> <title>ExpForm Test Code</title> <style> th { text-align: left; background: #ddd;} </style> <body> <form id="fm1"> <h3>原本のフォーム</h3> <table border="1"> <thead> <tr> <th>Elements Type <th>name <th>Default Values <tbody> <tr> <th>text <td>aaa <td><input type="text" name="aaa" value="0"> <tr> <th>checkbox <td>bbb <td> <label><input type="checkbox" name="bbb" value="1">Val=1</label> <label><input type="checkbox" name="bbb" value="2">Val=2</label> <tr> <th>radio <td>ccc <td> <label><input type="radio" name="ccc" value="3">Val=3</label> <label><input type="radio" name="ccc" value="4" checked>Val=4</label> <tr> <th>select <td>ddd <td> <select name="ddd"> <option value="5">5 <option value="6" selected>6 </select> <tr> <th>select-multiple <td>eee <td> <select name="eee" multiple> <option value="7" selected>7 <option value="8">8 <option value="9" selected>9 </select> <tr> <th>hidden <td>fff <td> <input type="hidden" name="fff" value="10"> <tr> <th>textarea <td>ggg <td> <textarea name="ggg" cols="30" rows="3">11</textarea> <tr> <th>All <td>hhh <td> <input type="text" name="hhh" value=""><br> <textarea name="hhh" cols="10" rows="3"></textarea><br> <label><input type="checkbox" name="hhh" value="3">Val=3</label> <label><input type="checkbox" name="hhh" value="4">Val=4</label><br> <select name="hhh" multiple> <option value="5">5 <option value="6">6 <option value="7">7 </select><br> <input type="text" name="hhh" value=""> <tr> <th>submit, reset, button <td>za[a-c] <td> <input type="button" name="zaa" value="copy_values"> <input type="submit" name="zab" value="submit"> <input type="reset" name="zac" value="reset"> </tbody> </table> </form> <form id="fm2"> <h3>コピーされるフォーム<h3> <table border="1"> <thead> <tr> <th>Elements Type <th>name <th>Default Values <tbody> <tr> <th>text <td>aaa <td><input type="text" name="aaa" value="0"> <tr> <th>checkbox <td>bbb <td> <label><input type="checkbox" name="bbb" value="1">Val=1</label> <label><input type="checkbox" name="bbb" value="2">Val=2</label> <tr> <th>radio <td>ccc <td> <label><input type="radio" name="ccc" value="3">Val=3</label> <label><input type="radio" name="ccc" value="4">Val=4</label> <tr> <th>select <td>ddd <td> <select name="ddd"> <option value="5">5 <option value="6">6 </select> <tr> <th>select-multiple <td>eee <td> <select name="eee" multiple> <option value="7">7 <option value="8">8 <option value="9">9 </select> <tr> <th>hidden <td>fff <td> <input type="hidden" name="fff" value="10"> <tr> <th>textarea <td>ggg <td> <textarea name="ggg" cols="30" rows="3"></textarea> <tr> <th>All <td>hhh <td> <input type="text" name="hhh" value=""><br> <textarea name="hhh" cols="10" rows="3"></textarea><br> <label><input type="checkbox" name="hhh" value="3">Val=3</label> <label><input type="checkbox" name="hhh" value="4">Val=4</label><br> <select name="hhh" multiple> <option value="5">5 <option value="6">6 <option value="7">7 </select><br> <input type="text" name="hhh" value=""> <tr> <th>submit, reset, button <td>za[a-c] <td> <input type="button" name="zaa" value="copy_values"> <input type="submit" name="zab" value="submit"> <input type="reset" name="zac" value="reset"> </tbody> </table> </form> <script> { const TARGET_PARTS = ['INPUT', 'TEXTAREA', 'SELECT'],//対象となる form要素の部品 EXCLUDE_TYPE = ['submit', 'reset', 'button', 'image', 'fieldset', 'file'];//部品から除外する //引数の要素が formの部品なのか? function isParts (e) { return (e) ? (TARGET_PARTS.includes (e.tagName)) ? ! EXCLUDE_TYPE.includes (e.type) : false : false; } //引数がノードリストなのか? function isNodeList (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; } //要素から使用可能な要素を取り出して配列化する function pickupParts (es) { return (isNodeList (es) ? Array.from (es): [es]).filter (isParts); } //引数の型を返す function typeOf (arg) { return Object.prototype.toString.call (arg).slice (8, -1); } //配列の中を文字列に変換する function insideArrayToString (ary) { return ary.map (v => String (v)); } //OPTION@SELECTの値を取得する function getOptionValue (opt) { return opt.hasAttribute ('value') ? opt.value : opt.text; } //同じ名前を持つ複数の部品がある場合並び替える function sortByType (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 をセットする */ function setCheckbox (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を設定 function setSelected (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; } //部品の値を返す function value (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の部品に値をセットする function setVal (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; } } } class ExpForm { /* 引数を省略すると documentにある最初の form要素が対象となる */ constructor (form = document.forms[0]) { if ('FORM' !== form.tagName) throw new Error ('FORM要素ではありません'); this.form = form; } //__________________________ /*フォームの部品の値を取得する 引数の型により戻り値の型が変化する 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); console.log ("rstary=",rst); 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).map ((key, i) => setVal (es[key], arg[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 () { this.form.reset (); } //__________________________ //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; } </script>