FormTableをAjax化する際の注意点

click-extrasには、表形式のフォームを実現するための、FormTableというコンポーネントがあります。
http://click.avoka.com/click-examples/table/form-table.htm
これはTableを拡張したコンポーネントなので、前回の記事にあるようにページリンクのAjax化が可能です。
また、サブミットボタンをAjax化したいというニーズもあるでしょう。
(これは、通常のSubmitをS2Clickに含まれるAjaxSubmitに置き換えれば実現できます。)
ただ、やってみるといくつか注意しなければいけないポイントがありましたので、順に説明します。

フォーム要素にDateFieldが含まれる場合の注意

DateFieldを使用すると、カレンダーをセットアップするためのscriptタグがDateFieldの数だけHTMLに追加され、ページがロードされたときに1回ずつ実行されます。

<head>

<script id="table_form_date_0-js-setup" type="text/javascript">
Click.addLoadEvent(function(){
Event.observe('table_form_date_0-button', 'click', function(){ calendar = new CalendarDateSelect($('table_form_date_0'), {  minute_interval: 1, popup_by: 'table_form_date_0-button',  embedded: false,  footer: false,  buttons: false,  time: false,  formatValue: 'yyyy/MM/dd',  year_range: [1930,2050] });});});
</script>
<script id="table_form_date_1-js-setup" type="text/javascript">
Click.addLoadEvent(function(){
Event.observe('table_form_date_1-button', 'click', function(){ calendar = new CalendarDateSelect($('table_form_date_1'), {  minute_interval: 1, popup_by: 'table_form_date_1-button',  embedded: false,  footer: false,  buttons: false,  time: false,  formatValue: 'yyyy/MM/dd',  year_range: [1930,2050] });});});
</script>

ところが、AjaxでFormTableのHTMLの置き換えを行うと、これらのscriptタグが実行されないので、カレンダーのアイコンをクリックしても、カレンダーが開かなくなります。
そこで、FormTableのHTMLをAjaxのレスポンスとして戻す際、その中に含まれるDateFieldセットアップ用のJavaScriptもレスポンスの一部として戻し、Ajaxのレスポンスを処理する際に実行してあげます。
まず、Java側でレスポンスを戻す際に、FormTableのHTMLとともに、そのheaderElementに含まれるDateFieldセットアップ用のJavaScriptを収集し、配列として戻します。

     public boolean onPageMove() {
          String tableHTML = table.toString();
          Set<String> setupJsScripts = getSetupJsScripts();
          Map<String, Object> result = new HashMap<String, Object>();
          result.put("tableHTML", tableHTML);
          result.put("setupJsScripts", setupJsScripts);
          renderJSON(result);
          return false;
     }

     private Set<String> getSetupJsScripts() {
          Set<String> scripts = new HashSet<String>();
          for (Element e : table.getHeadElements()) {
               if (e instanceof JsScript) {
                    JsScript js = (JsScript) e;
                    if (js.getId() != null && js.getId().endsWith("-js-setup")) {
                         scripts.add(js.getContent());
                    }
               }
          }
          return scripts;
     }

JavaScript側でレスポンスを処理する際に、HTMLの置き換えを行った後、受け取ったJavaScriptの配列の中身を全て実行します。

var movePageComplete = function (res) {
     var json = eval('(' + res.responseText + ')');
     $('tab').innerHTML = json['tableHTML'];
     executeSetupScripts(json['setupJsScripts']);
...
}
var executeSetupScripts = function (scripts) {
     for (var i = 0; i < scripts.length; i++) {
          eval(scripts[i]);
     }
}

SubmitをAjaxSubmitに置き換える場合の注意

AjaxSubmitを使用すると、それが含まれるフォームのonsubmitにAjax呼び出しを行って本来のサブミット動作をキャンセルするイベントハンドラを登録するscriptタグが追加され、ページがロードされたときに1回だけ実行されます。

<tr class="buttons"><td class="buttons"><input type="submit" name="ajaxSubmit" id="table_form_ajaxSubmit" value="Ajax Submit"/><script type="text/javascript">
$('table_form').onsubmit = function(){
  this.request({ method: 'post', onComplete: ajaxSubmitComplete}); return false;}
</script>
</td></tr>

しかし、AjaxでFormTableのHTMLの置き換えを行った場合、そのscriptタグが実行されないので、ボタンを押すと普通にフォームがサブミットされてしまいます。
これを防ぐため、HTMLの置き換えを行った後、FormTable内のフォームのonsubmitに上記イベントハンドラを追加します。
"table-form"は、FormTableのformタグに自動的に割り振られるidです。

var movePageComplete = function (res) {
     var json = eval('(' + res.responseText + ')');
     $('tab').innerHTML = json['tableHTML'];
...
     $('table_form').onsubmit = function () {
          this.request({ method: 'post', onComplete: ajaxSubmitComplete});
          return false;
     };
}

※このコードだけを実行時にAjaxSubmitから取得することが難しかったので、出力されたHTMLから抽出しました。ただし、フォームのID等は変わる可能性がありますので、もう少し良いやり方があるかもしれません。

以下、今回の全てのサンプルコードです。