複数行の繰り返し入力フォーム

1つの文書内に可変行数の入力項目を入れる方法は、応用その7: サブ項目のあるXPageで説明しているが、今度は、可変行の中に直接入力できるインタフェースを作ってみよう。

複数行の入力項目

基本的な考え方

繰り返しコントロールを使えば、入力項目を並べた表を作ることは簡単にできる。

まずは、データの入れ物をsessionScope内に適当な名前で用意する。 ページを開いたときには、文書の所定のフィールドからデータを読み出し、sessionScopeに格納しておく。

ページ内の、データを表示する繰り返しコントロールでsessionScopeに格納した繰り返し

ページの保存時には、sessionScopeのデータを文書に保存する。 あとは、ページ内でデータを編集できるようにすればいい。

繰り返しデータの格納

既存の文書を開いたときに、文書内の所定のフィールドからデータを読み出し、sessionScopeのデータ格納用のメンバー変数にセットする方法を説明する。

この手の処理は文書タイプのデータソースのpostOpenDocumentイベントに記述するのが定石。[item_list_json]フィールドにセットされている文字列をfromJson()でJavaScriptのオブジェクトに変換し、 sessionScope.item_listに格納している。

  var item_list_json = document1.getItemValueString("item_list_json");
  var item_list = fromJson(item_list_json);
  sessionScope.item_list = item_list;
  

新規文書作成用には、postNewDocumentイベントに記述する。こちらは既存のデータがないので、sessionScope.item_list = []で十分。

繰り返しデータの編集

繰り返しコントロールの内側に配置した入力系のコントロール(xp:inputTextなど)で入力したデータはsessionScope.item_listに反映する必要がある。 繰り返しコントロールの各要素にアクセスするために指定するコレクション名が"item"だとすると、item.nameなどにデータを格納すればいい。 ということは、入力用コレクションのデータをitem.nameなどにバインドすればいい。

データとの紐付け方法は、入力系コントロールの「データ」タブで指定できるのだが、JavaScriptでitem.nameのように指定すればいいような気がするのだが、この方法ではうまくいかない。

繰り返しコントロール内のデータソースの指定

別にエラーにもならないのだが、実際にやってみると以下の問題が起きる。

いろいろ試してみた感じでは、ソースコードタブで以下のように書き換えないといけない。

  変更前: <xp:inputText id="eventDate1" value="#{javascript:item.eventDate}">
  変更後: <xp:inputText id="eventDate1" value="#{item.eventDate}">
  

ただし、編集モードと表示モードの切り替えは、自分でやらないといけない。 現在のモードはdocument1.isEditable()でわかるので、読み取り専用の設定で切り替えればいい。

こうすれば、入力枠に入れたデータは、自然にsessionScope.item_list内の対応する要素に格納されるようになる。

行数を増減させる方法

最後に、行数を増減させる方法を組み込む。

行を減らすには、sessionScope.item_listから、削除したい行に対応する要素を削除してしまえばいい。n行目を削除したいなら、"sessionScope.item_list.remove(n);"でOK. 各行の先頭に[DEL]ボタンを配置しておきう、 自分の行が何列目かを知ることができればいい。自分が何行目かは、繰り返しコントロールの「索引名」が教えてくれる。この例なら、DELボタンに、"sessionScope.item_list.remove(item_index);"をセットしておけばいい。

繰り返しコントロールの索引名

次に行を追加する方法だが、どこかに[追加]ボタンを用意して、sessionScope.item_listに要素を1つ追加すればいい。 今回は、一番下の行に[追加]ボタンを置くことにする。ボタンの表示/非表示はスクリプトで制御できるので、一番下にだけ[追加]ボタンを表示させるのは難しくない。 [追加]の処理は、単にsessionScope.item_listに要素を1つ追加するだけでいいので、"sessionScope.item_list.push({})"で基本的にはかまわない。

ソースコード

ソースコードは以下の通り。my_inspect()はデバッグ用の関数。 サーバサイドスクリプトのデバッグ参照。 CSSはまぁ適当に。

  001: <?xml version='1.0' encoding='UTF-8'?>
  002: <xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex">
  003:   <xp:this.data>
  004:     <xp:dominoDocument var="document1" formName="Event">
  005:       <xp:this.postOpenDocument>
  006:         <![CDATA[
  007:           #{javascript:
  008:             var item_list_json = document1.getItemValueString("item_list_json");
  009:             var item_list = fromJson(item_list_json);
  010:             sessionScope.item_list = item_list;
  011:           }
  012:         ]]>
  013:       </xp:this.postOpenDocument>
  014:       <xp:this.postNewDocument>
  015:         <![CDATA[
  016:           #{javascript:
  017:             sessionScope.item_list = [{}];  
  018:             sessionScope.debug1 = "初期設定。sessionScope.item_list = " + my_inspect(sessionScope.item_list);
  019:           }
  020:         ]]>
  021:       </xp:this.postNewDocument>
  022:     </xp:dominoDocument>
  023:   </xp:this.data>
  024:   <xp:this.resources>
  025:     <xp:styleSheet href="/mycss.css" />
  026:     <xp:script src="/mylib.jss" clientSide="false" />
  027:   </xp:this.resources>
  028:   <xp:button id="button2" value="Save">
  029:     <xp:eventHandler refreshMode="complete" submit="true" event="onclick">
  030:       <xp:this.action>
  031:         <![CDATA[
  032:           #{javascript:
  033:             var item_list = [];
  034:             for(var index =0; index < sessionScope.item_list.length; index++) {
  035:               item_list.push(sessionScope.item_list[index]);
  036:             }
  037:             var item_list_json = toJson(item_list);
  038:             document1.setValue("item_list_json", item_list_json);
  039:             document1.save();
  040:             context.setDocumentMode("readOnly");
  041:             sessionScope.debug1 = "保存。JSON = " + item_list_json;
  042:           }
  043:         ]]>
  044:       </xp:this.action>
  045:     </xp:eventHandler>
  046:   </xp:button>
  047:   <xp:button id="button5" value="Edit">
  048:     <xp:eventHandler refreshMode="complete" submit="true" event="onclick">
  049:       <xp:this.action>
  050:         <![CDATA[
  051:           #{javascript:
  052:             context.setDocumentMode("edit");
  053:           }
  054:         ]]>
  055:       </xp:this.action>
  056:     </xp:eventHandler>
  057:   </xp:button>
  058:   <xp:table styleClass="inputFormTbl">
  059:     <xp:tr>
  060:       <th style="width:20.0px">No.</th>
  061:       <th>Event date</th>
  062:       <th>Subject</th>
  063:       <th>Keyword</th>
  064:       <th>Name</th>
  065:     </xp:tr>
  066:     <xp:repeat id="repeat1" value="#{sessionScope.item_list}" rows="30" var="item" indexVar="item_index">
  067:       <xp:tr>
  068:         <xp:td>
  069:           <xp:label id="label1">
  070:             <xp:this.value>
  071:               <![CDATA[
  072:                 #{javascript:
  073:                   "" + (item_index+1)
  074:                 }
  075:               ]]>
  076:             </xp:this.value>
  077:           </xp:label>
  078:           <xp:button id="button6" value="追加">
  079:             <xp:this.rendered>
  080:               <![CDATA[
  081:                 #{javascript:
  082:                   if (item_index == (sessionScope.item_list.length - 1)) {
  083:                     return true;  // 見える
  084:                   } else {
  085:                     return false; // 見えない
  086:                   }
  087:                 }
  088:               ]]>
  089:             </xp:this.rendered>
  090:             <xp:eventHandler refreshMode="complete" submit="true" event="onclick">
  091:               <xp:this.action>
  092:                 <![CDATA[
  093:                   #{javascript:
  094:                     // 次の行を追加する
  095:                     var new_item = {};
  096:                     sessionScope.item_list.push(new_item);
  097:                   }
  098:                 ]]>
  099:               </xp:this.action>
  100:             </xp:eventHandler>
  101:           </xp:button>
  102:           <xp:button id="button3" value="DEL">
  103:             <xp:this.rendered>
  104:               <![CDATA[
  105:                 #{javascript:
  106:                   if (item_index == (sessionScope.item_list.length - 1)) {
  107:                     return false;  // 見えない
  108:                   } else {
  109:                     return true;  // 見える
  110:                   }
  111:                 }
  112:               ]]>
  113:             </xp:this.rendered>
  114:             <xp:eventHandler refreshMode="complete" submit="true" event="onclick">
  115:               <xp:this.action>
  116:                 <![CDATA[
  117:                   #{javascript:
  118:                     sessionScope.item_list.remove(item_index);
  119:                   }
  120:                 ]]>
  121:               </xp:this.action>
  122:             </xp:eventHandler>
  123:           </xp:button>
  124:         </xp:td>
  125:         <xp:td>
  126:           <xp:inputText readonly="#{javascript:!document1.isEditable()}" id="eventDate1" value="#{item.eventDate}" style="width:150.0px">
  127:             <xp:this.rendered>
  128:               <![CDATA[
  129:                 #{javascript:
  130:                   if (item.index2 != "追加") {
  131:                     return true;
  132:                   } else {
  133:                     return false;
  134:                   }
  135:                 }
  136:               ]]>
  137:             </xp:this.rendered>
  138:             <xp:dateTimeHelper id="dateTimeHelper1" />
  139:             <xp:this.converter>
  140:               <xp:convertDateTime dateStyle="short" type="date" />
  141:             </xp:this.converter>
  142:           </xp:inputText>
  143:         </xp:td>
  144:         <xp:td>
  145:           <xp:inputText readonly="#{javascript:!document1.isEditable()}" id="subject1" value="#{item.subject}">
  146:             <xp:this.rendered>
  147:               <![CDATA[
  148:                 #{javascript:
  149:                   if (item.index2 != "追加") {
  150:                     return true;
  151:                   } else {
  152:                     return false;
  153:                   }
  154:                 }
  155:               ]]>
  156:             </xp:this.rendered>
  157:           </xp:inputText>
  158:         </xp:td>
  159:         <xp:td>
  160:           <xp:inputText readonly="#{javascript:!document1.isEditable()}" id="keyword1" value="#{item.keyword}">
  161:             <xp:this.rendered>
  162:               <![CDATA[
  163:                 #{javascript:
  164:                   if (item.index2 != "追加") {
  165:                     return true;
  166:                   } else {
  167:                     return false;
  168:                   }
  169:                 }
  170:               ]]>
  171:             </xp:this.rendered>
  172:           </xp:inputText>
  173:         </xp:td>
  174:         <xp:td>
  175:           <xp:inputText readonly="#{javascript:!document1.isEditable()}" id="inputText1" value="#{item.name}">
  176:             <xp:this.rendered>
  177:               <![CDATA[
  178:                 #{javascript:
  179:                   if (item.index2 != "追加") {
  180:                     return true;
  181:                   } else {
  182:                     return false;
  183:                   }
  184:                 }
  185:               ]]>
  186:             </xp:this.rendered>
  187:           </xp:inputText>
  188:         </xp:td>
  189:       </xp:tr>
  190:     </xp:repeat>
  191:   </xp:table>
  192:   <xp:button id="button4" value="SHOW">
  193:     <xp:eventHandler refreshMode="complete" submit="true" event="onclick">
  194:       <xp:this.action>
  195:         <![CDATA[
  196:           #{javascript:
  197:             sessionScope.debug1 = "\nsessionScope.item_list = " + my_inspect(sessionScope.item_list);
  198:           }
  199:         ]]>
  200:       </xp:this.action>
  201:     </xp:eventHandler>
  202:   </xp:button>
  203:   <xp:inputTextarea id="inputTextarea1" value="#{sessionScope.debug1}" style="width:443.0px;height:117.0px" />
  204: </xp:view>