2015/05/05

[HTML/javascript]“同一formの別のselectの操作”を複数のformについて一つの関数で行う。

この記事は以前製作した艦これ対空値計算スクリプトを事例に書いているため、艦これを知らない人にはわかりにくい説明となっている可能性があります。
一般性をもたせた結論はこちらになりますので、すっ飛ばして読んでいただいたほうがわかりやすいかもしれません。

艦これ:制空値算出は、6隻分の対空値を計算するために6つのformを使用している。
それぞれの艦について1つずつformを使用し、艦1から6まで、それぞれ「ship1」から「ship6」までのnameが付与されている。

一方、このスクリプトに必要な機能(関数)は以下の

  • 「艦種」が選択された際に、それに応じた「艦名」をセットする→setShipvar関数
  • 「艦名」が選択された際に、それに応じた「艦載機の積載数」をセットする→setShipname関数、loadsubmit関数
  • 「艦載機」が選択された際に、それに応じた「対空値」をセットする→setAircraft関数、submitAircraft関数

3つの機能が必要だ(本当はページ読み込み時に「艦種」に「正規空母」などをセットする関数と、対空値を計算する関数もあるがそれは今回の内容とは関係ないので割愛する)。

これらの関数を6つのform分用意するのは面倒くさい。しかし、積載数は6隻分別々に用意された行列に登録しなければならない。
そしてこれは一つのform内でも同じ問題が生じる。スロットは4つあり、対空値を登録するときには当然、それに応じた行列に値を登録しなければならない。となると、スロット4つ分、関数を用意することになってしまうが、それもやはり冗長だ。

故に、ない頭を振り絞って考える。どうにかこれらの関数を一つにまとめることはできないか…。難しいのは、selectで何かが選択された時に、同一form内の別のselectの内容を変更させたい、という部分だ。具体的には、艦種で「正規空母」を選んだら艦名には「赤城改」などをセットし、「軽空母」を選んだら「鳳翔改」などをセットする、といった具合だ。別のselectの内容を変更する以上、そのselectをidなりnameなりで特定しなければならない。それをform6つ分、一つの関数で処理する、というところが難しい。
いろいろ考えて、一つの解決策を思いついた。鍵となったのはformタグでselectタグを囲んでいることであった。

formは艦ごとに用意されているから、そのnameを確認することで何隻目を選択しているのか確認することができる。具体的にはこうした。

function setShipvar(selected){
 var num = selected.selectedIndex;
 var shipvar = selected.options[num].text;
 var formobj = selected.parentNode;
 var jid = formobj.shipname.id;

 switch(shipvar){
  case '正規空母':
   //"jid"を使って正規空母が選択された時の処理を記述。jidは、この関数を起動したformにおける艦名選択のselectのid。

鍵となっているのはvar formobj = selected.parentNode; と var jid = formobj.shipname.id;だ。同時に、関数に引数を設定した。それぞれのselectタグは、何か項目を選択されたときに自分自身を引数として関数を呼び出すようにした。

例えば艦1のformで艦種「正規空母」を選んだとする。(初期段階では空欄の選択肢だが、ページ読込完了時に艦種を6つのformにまとめてセットするようにしている。)するとonChangeで設定されている関数が呼び出される。この場合、自身を引数としてsetShipvar関数が呼び出される。
関数側では渡された引数をselectedという名前で扱っている。これでひとまず、setShipvar関数で6隻分扱うことについての最初の一歩を踏み出したことになる。
selected = document.ship1.shipvar なので、選ばれた艦種は

var num = selected.selectedIndex;
var shipvar = selected.options[num].text;

で取得できる。これで「shipvar」には「正規空母」が代入されていることになる。
次なる問題は、どうやって「艦名」のselectを取得するのか、ということになる。6隻分の艦名selectに一意のidを付与すれば取得できるが、それでは関数をひとつにまとめることができない。ここを前述の「鍵」でクリアすることにした。

var formobj = selected.parentNode;
var jid = formobj.shipname.id;

ここでは「var formobj = selected.parentNode;」でselected、即ち引数として渡されたselectタグの親となるタグ、つまりformタグを取得できる。艦1のformであれば「document.ship1」に当たる部分ということだ。
次に「var jid = formobj.shipname.id;」がある。これは艦種が選択されたformの、shipnameというnameを持つselect(つまり一つ下のselect)のidをjidに代入する、という意味だ。あとはこのjidを使ってjQueryを使うなりなんなりして、selectを操作できる。
formで囲んだselectを扱う際には、「document.(form名).(select名)」と言った形で扱う。となれば、form名は一意の名前とし、同じ機能を必要とするselectの名前を同じにすると、form名を指定すれば特定のselectを取得することができ、かつ関数を一つにまとめることができる。この仕様を使って、一意のidを付与しつつ、そのidを直接指定することなく関数を設計することで、複数のformで扱える関数を作ることができた。


結論
  • 同じ操作を必要とするselectは複数のformに渡って同じnameにする。
  • selectのonChangeで関数を呼び出す際、自身を引数にする。
  • 関数側で引数のparentNode(formオブジェクト)を取得し、それを基に別のselectのnameを指定すれば、ベタでIDやnameを指定することなく複数のformに関して、一つの関数で別のselectの操作を行える。

正直言ってjavascriptでやるには不向きなものだったような気もする。

0 件のコメント:

コメントを投稿