はじめに
formをsubmitすると、formに属する全要素のデータがサーバに送られる。
特定の要素を除外してformをsubmitすることは通常はできないため、このような目的には、本来であればformを複数に分割して各処理ボタンを押した時に必要な要素のみサーバに送られるようにすべきである。
特定の要素を除いてsubmitしたいという目的が無くても、通信データ量の観点からみると、やはり適切に分割された複数のformがある方がいい。
とはいっても何らかの事情で、全部入りのform一個だけしかない場合などでも、一部のデータを送らないようにするためにはどうすればいいか考えてみた。
Ajaxによる解決
解決方法としてまず考えられるのが、Ajax通信でpostする方法。
jquery#serialize, serializeArrayでformを文字列にシリアライズするときに、特定のnameを除く方法 で書いたように、postするデータを作成するときに特定の要素を省いてあげればいい。
しかし、アプリのつくり上、Ajax通信ではなく通常のsubmitでpostしなければならないとなった場合、通常のJavaScript、HTMLの仕組みでは対応できない。
jQueryでformを作成してPOST(submit)する で、動的にformを作成すればいいのかとヒントを得たので作成してみる。
使用したメインのJavaScript関数は cloneNode(include_child)
で、同じ機能が .clone( [withDataAndEvents ] )
という関数でjQueryにあったので、jQuery無と有で両方を試した。
jQuery無のバージョン
function submit(excludeIds) {
var srcNode = document.getElementsByTagName("form")[0];
// formをコピーする。引数にtrueを入れているため、子要素を含む。
var copyNode = srcNode.cloneNode(true);
// 除外elementリスト
var excludes = [];
for (var i = 0; i < copyNode.length; i++) {
// 複製したformに含めたくないものを除外elementリストに詰める。
if (excludeIds.indexOf(copyNode[i].id) > -1) {
excludes.push(copyNode[i]);
}
// IDは一意であるべきなので、複製したものには独自のネーミングルールを適用する。
if (copyNode[i].id != "undefined" && copyNode[i].id != "") {
copyNode[i].id += "copy_";
}
// ブラウザによって、radioとselect等の選択した値がコピーされないので、対処
// 以下はIE8で動作確認済み
if (typeof(copyNode[i].checked) != 'undefined') {
copyNode[i].checked = srcNode[i].checked;
}
if (copyNode[i].tagName.toLowerCase() == 'select') {
for (var si = 0; si < srcNode[i].options.length; si++) {
copyNode[i].options[si].selected = srcNode[i].options[si].selected;
}
}
}
// 除外elementすべてを取り除く
for (var i = 0; i < excludes.length; i++) {
removeElement(excludes[i]);
}
// submitのためだけにコピーしているので、非表示にする。
copyNode.style.display = "none";
// コピーしたformをbodyに追加
document.body.appendChild(copyNode);
copyNode.submit();
}
function removeElement(node) {
node.parentNode.removeChild(node);
}
参考/引用
removeElement(node)の出典元:http://stackoverflow.com/questions/13763/how-do-i-remove-a-child-node-in-html-using-javascript
cloneNodeで選択状態をうまくコピーできない問題の参考元:http://www.programming-magic.com/20071205185601/
jQuery有のバージョン
// Textarea and select clone() bug workaround | Spencer Tipping
// Licensed under the terms of the MIT source code license
// Motivation.
// jQuery's clone() method works in most cases, but it fails to copy the value of textareas and select elements. This patch replaces jQuery's clone() method with a wrapper that fills in the
// values after the fact.
// An interesting error case submitted by Piotr Przyby?: If two <select> options had the same value, the clone() method would select the wrong one in the cloned box. The fix, suggested by Piotr
// and implemented here, is to use the selectedIndex property on the <select> box itself rather than relying on jQuery's value-based val().
(function(original) {
jQuery.fn.clone = function() {
var result = original.apply(this, arguments),
my_textareas = this.find('textarea').add(this.filter('textarea')),
result_textareas = result.find('textarea').add(result.filter('textarea')),
my_selects = this.find('select').add(this.filter('select')),
result_selects = result.find('select').add(result.filter('select'));
for (var i = 0, l = my_textareas.length; i < l; ++i) $(result_textareas[i]).val($(my_textareas[i]).val());
for (var i = 0, l = my_selects.length; i < l; ++i) {
for (var j = 0, m = my_selects[i].options.length; j < m; ++j) {
// patch for multiselect
// if (my_selects[i].options[j].selected === true) {
// result_selects[i].options[j].selected = true;
// }
result_selects[i].options[j].selected = my_selects[i].options[j].selected;
}
}
return result;
};
})(jQuery.fn.clone);
function submit(excludeIds) {
var srcNode = $("form:first");
var copyNode = srcNode.clone(true);
$.each(excludeIds, function(index, excludeId) {
var excludes = copyNode.find("#" + excludeId);
$.each(excludes, function(index, elem) {
$(elem).remove();
});
});
copyNode.find("[id]").map(function (index, elem) {
var idHolder = $(elem);
idHolder.attr("id", "copy" + idHolder.attr("id"));
});
copyNode.css("display", "none");
copyNode.appendTo(document.body);
copyNode.submit();
}
参考/引用
cloneの拡張コード出典元:https://github.com/spencertipping/jquery.fix.clone
cloneの拡張コード参考元:http://stackoverflow.com/questions/742810/clone-isnt-cloning-select-values
jQuery版も処理の流れは、jQuery無のバージョンと同じだが、clone特有の選択状態をうまくコピーできないバグを修正したコード(jquery.fix.clone)があったので、それを利用した。
ただ jquery.fix.clone は、select要素にmultiple属性が指定されている場合(multiselect
)を考慮していないようだったので、一部修正を加えた。
multiselect
の場合の具体的な影響は以下の通り。(IE8で検証)
- ある値(A)を選択して、サーバに送る。
- 画面がリフレッシュし、selectのAの初期値がselectedになる。
- Aの選択をクリアし、別の値(B)を選択する。
- cloneすると、Aの初期値はselectedのためclone先もselected、Bは(selected == true)の条件に当てはまるのでclone先にはselectedが適用される。
- 正解はBのみselectedなのに、AもBもselectedになる。
jQuery版をここからさらに改良するとすれば、submit(excludeIds)
の引数部分がポイントとなる。
今回作成した関数のinterfaceを、jQuery無のバージョンと有のバージョンで合わせるため(引数がidの文字列の配列)に、jQuery版のコードで find("#" + excludeId)
と汚いコードになったが、別にinterfaceを合わせる必要はない。
submit(excludeSelectors)
としてあげれば、jQuery的な汎用性のある関数になる。