Drupal 7 における AJAX フォーム

~ AJAX Forms in Drupal 7 和訳 ~

AJAXフォームとは

Drupal 7 における AJAXフォームとは、ページをリロードすることなく動的に変化させるようなフォームのことで、簡単に生成、操作することが出来ます。これは Drupal のフォームAPI のシンプルな拡張機能です。

動的に変化するフォームとは何でしょう?従来のウェブでは、ユーザーがフォームに入力して送信ボタンを押すと、ページ全体が再構築されてブラウザに送られてきました。AJAX フォームでは、ページ全体を再読み込みすることなく、ページの一部、又はフォームの一部だけが更新されたり、他の要素と置き換えられます。つまり変更箇所だけがその場で変更されます。これはユーザーにとって扱いやすく、一般的にはページ全体を再読み込みするより速いです。

AJAX についての事実:

  • AJAXフォームは、ページ全体を再読み込みすることなく、動的に変化するフォームです。
  • これらの実装は Drupal 7 では大幅に簡素化されています。
  • 開発者は AJAXフォームを使うのに、javascript を触る必要は無く、めんどうな処理はすべて Drupal が行います。
  • AJAXフォームはマルチステップフォームの処理に大変よく似ています。
  • 多くの場合、AJAXフォームはページの中の HTML 要素が置き換えられたものであり、その多くは再構築されたフォームの一部です。

いくつかの背景

  • Drupal 7 以前においては、AJAXフォームは AHAHフォームと呼ばれていました。これは AJAX (Asynchronous Javascript and XML) という名前が XML が関係することを示唆しているにもかかわらず、Drupal のこの技術においては XML の形式で非同期通信を行うことはなかったためです。Drupal 7 では、用語のニュアンスが変わり、厳密には正確でなくとも、このような動作をするものを一般的に AJAX と呼ぶようになった為、AJAXフォームと呼びます。また Drupal 7 以前では、バックグラウンドでフォームの整合性を保つためにいくつかのカラクリが必要でしたが、こういった事はすべて標準化され、Drupal コアのコードに組み込まれたので、開発者がこれについて個々に悩む必要はなくなりました。
  • AJAXフォームとは全く無関係に動作する AJAX の例もたくさんあります。たとえば Fivestarモジュールは、ページの再読み込み無しでブラウザー/サーバー間のコミュニケーションを取るのに、独自の AJAX 実装を使用します。

動作の概観

動作の概観は次のようになります。

  1. セレクトボックス、 サブミットボタンなどのフォーム要素を操作することによって、サーバー側でフォームが再構築されます。
  2. サーバー側のフォームビルダーは、送られてきた入力内容($form_state)に応じて、異なるフォームを構築します。
  3. #ajax の設定およびコールバック関数により、ページの一部を置き換え・更新するための新たに構築されたフォームの一部が届けられます。

基本

AJAX 対応のフォームを作るには次のようにします。

  • AJAX を利用するフォーム要素に #ajax プロパティーを追加します。このフォーム要素は、値が変更されたり、クリックされたりしたときに、バックグラウンドで非同期通信を行うトリガーとなります。
    • #ajax['wrapper'] プロパティは、ページの中で置き換えられる箇所(または何らかの形で更新される箇所)の HTML ID を指定します。
    • #ajax['callback'] プロパティは、非同期通信によりフォームが再構築された後、コールすべきコールバック関数を指定します。
  • コールバック関数を記述します。これは一般的には、オリジナルページの一部を置き換えるための、フォームの一部を返すシンプルな関数です。

Example モジュールの「AJAX Example: generate checkboxes」の例では、AJAX を利用するフォーム要素は $form['howmany_select'] というセレクトボックスで、これは 'checkboxes-div' という ID を持つHTML 要素(#ajax['wrapper'] で指定されている、$form['checkboxes_fieldset'] フィールドセットのラッパー)を置き換えます。

<?php
/**
 * AJAX のセレクトボックスの操作に応じて、チェックボックスのフィールドセット部分を置き換えます。
 */
function ajax_example_autocheckboxes($form, &$form_state) {

  $default = !empty($form_state['values']['howmany_select']) ? $form_state['values']['howmany_select'] : 1;

  $form['howmany_select'] = array(
    '#title' => t('How many checkboxes do you want?'),
    '#type' => 'select',
    '#options' => array(1 => 1, 2 => 2, 3 => 3, 4 => 4),
    '#default_value' => $default,
    '#ajax' => array(
      'callback' => 'ajax_example_autocheckboxes_callback',
      'wrapper' => 'checkboxes-div',
      'method' => 'replace',
      'effect' => 'fade',
    ),

  );


  $form['checkboxes_fieldset'] = array(
    '#title' => t("Generated Checkboxes"),
    // prefix/suffix で置換対象の div 要素を設定します。
    // これは上記の #ajax['wrapper'] で指定したものです。
    '#prefix' => '<div id="checkboxes-div">',
    '#suffix' => '</div>',
    '#type' => 'fieldset',
    '#description' => t('This is where we get automatically generated checkboxes'),
  );

  // 完全なコードは以下に!
?>

'howmany_select' 要素が変更されると、バックグラウンドでサーバーとの非同期通信が行われ、フォームが再構築されます。'howmany_select' の値をもとにフォームが再構築された後、コールバック関数がコールされます。

<?php
/**
 * 返り値はフォームの変更された部分のみです。
 * #ajax['callback'] は、HTMLかレンダー配列、もしくはコマンドの配列をフォームの一部として返します。
 */
function ajax_example_autocheckboxes_callback($form, $form_state) {
  return $form['checkboxes_fieldset'];
}
?>

このケースでは、コールバックは HTMLページの中で置き換え対象であるフォームの一部だけを返します。このあとレンダリングされた文字列がページに返され、#ajax['wrapper'] で指定した部分と置き換えられます。

これが AJAX フォームの動作の概観です。#ajax プロパティーの設定されたフォーム要素は、それがクリックされるか、変更された際に、そのアクションをトリガーとしてバックグラウンドでサーバーとの通信を行います。サーバー側では、フォームビルダー関数によってフォームが再構築され、#ajax['callback'] で指定されたコールバック関数がコールされます。コールバック関数はオリジナルページの代替部分として、再構築されたフォームの一部を返します。

詳細

上述の Example モジュールの例について、もう少し詳しく見てみましょう。(Example モジュールは現在もちゃんとメンテナンスがなされ配布されています。ダウンロードして実際にどのように動作するかを見ることができます。)

<?php
/**
 * AJAX のセレクトボックスの操作に応じて、チェックボックスのフィールドセット部分を置き換えます。
 */
function ajax_example_autocheckboxes($form, &$form_state) {

  $default = !empty($form_state['values']['howmany_select']) ? $form_state['values']['howmany_select'] : 1;

  $form['howmany_select'] = array(
    '#title' => t('How many checkboxes do you want?'),
    '#type' => 'select',
    '#options' => array(1 => 1, 2 => 2, 3 => 3, 4 => 4),
    '#default_value' => $default,
    '#ajax' => array(
      'callback' => 'ajax_example_autocheckboxes_callback',
      'wrapper' => 'checkboxes-div',
      'method' => 'replace',
      'effect' => 'fade',
    ),

  );


  $form['checkboxes_fieldset'] = array(
    '#title' => t("Generated Checkboxes"),
    // prefix/suffix で置換対象の div 要素を設定します。
    // これは上記の #ajax['wrapper'] で指定したものです。
    '#prefix' => '<div id="checkboxes-div">',
    '#suffix' => '</div>',
    '#type' => 'fieldset',
    '#description' => t('This is where we get automatically generated checkboxes'),
  );

  $num_checkboxes = !empty($form_state['values']['howmany_select']) ? $form_state['values']['howmany_select'] : 1;
  for ($i=1; $i<=$num_checkboxes; $i++) {
    $form['checkboxes_fieldset']["checkbox$i"] = array(
      '#type' => 'checkbox',
      '#title' => "Checkbox $i",
    );
  }

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

  return $form;
}

/**
 * 返り値はフォームの変更された部分のみです。
 * #ajax['callback'] は、HTMLかレンダー配列、もしくはコマンドの配列をフォームの一部として返します。
 */
function ajax_example_autocheckboxes_callback($form, $form_state) {
  return $form['checkboxes_fieldset'];
}
?>
  1. 初期化されたフォームがユーザーに表示されます。
  2. ここでは 'checkboxes-div' の ID を持つ DIV 要素によって、$form['checkboxes'] をラップしています。これは $form['checkboxes']['#prefix'] および $form['checkboxes']['#suffix'] によって設定されています。
  3. $form['howmany_select'] セレクトボックスを操作すると、フォームの内容を変更するために、バックグラウンドでサーバーとの通信が行われます。
  4. $form['howmany_select'] セレクトボックスの値でリクエストされた数だけチェックボックスを生成するように、フォームを再構築します。
  5. ajax_example_autocheckboxes_callback() がコールされ、再構築されたフォームの内のページの置き換えの部分を返します。 (これは #ajax['wrapper'] プロパティで指定した部分の要素と置き換えられます。)
  6. 返り値はレンダリングされ、ページに返されます。そしてページの 'checkboxes-div' DIV 要素と置き換えられます。

注意事項

  • フォームの内容の変更は、必ずフォームビルダー関数を通して行います。そうでないと検証を通りません。(この例では、ajax_example_autocheckboxes() の中で行うようにします。)コールバック関数内でフォームの変更や操作を行わないでください。
  • AJAX コールバックと#default_value: AJAXを使ってフォーム要素を変更する場合、#default_value の変更は自動的には反映されません。AJAXコールバックで #default_value の値を反映させるには、別の方法を使います。これについては次のリンクを参考にしてください。Form API: default value does not changeDefault_value not working for Radio Buttons in Ajax Callback
  • 内容の置き換えが可能なのは、フォーム要素とは限りません。設定するラッパーIDによっては、ページの中のあらゆる HTML 要素を変更することができます。
  • フォーム全体を置き換えることもできます。その場合はフォーム自体に #prefix と #suffix を設定してラッパーを追加し、そのラッパーの ID を #ajax['wrapper'] に指定します。(この方法では1度の非同期通信で、複数のフォーム要素を変更することができます。)但しサーバーとやり取りする情報量が増えれば、それだけ処理速度は遅くなります。
  • コールバック関数の中で扱う $form は、既に全てのフォーム構築プロセスを経ていることに注意してください( drupal_render() 以外)。要素のマークアップ文字列は単純にこうなります。
    <?php
      $elements['some_element']['#markup'] = 'New markup.';
      return $elements;
    ?>
    #attributes プロパティにすでに変換されている値を変更する際は、$form の配列も深く掘り下げ、対応する要素のプロパティーも同様に変更してください。
    <?php
      // 両方必要です。
      $elements['some_element']['#disabled'] = TRUE;
      $elements['some_element']['#attributes']['disabled'] = 'disabled';
      return $elements;
    ?>

ブラウザがJavaScriptに対応していない場合の対処法

ブラウザがJavaScriptに対応していない場合の対処法を考えてみましょう。AJAXフォームはこのために構築されましたが、JavaScriptが有効/無効の両方の環境で、フォームを簡単に、そして正確に動作させるには、かなりの労力がかかることがあります。多くの場合、非同期通信を行う要素には「次へ」ボタンが設置されます。ボタンが押されると、要素の値が変更されたときと同様に、ページ(フォーム)が再構築されます。Examples モジュールは ajax_example_graceful_degradation.inc ファイルでこのようないくつかの例を提供しています。

  • 「さらに追加」ボタン
  • 依存ドロップダウンの例
  • ダイナミックセクション
  • ウィザード(マルチステップフォーム)

AJAX機能のさらなる拡張

AJAX Framework では、基本的なフォームの動作に加えて、たくさんの機能やオプションを提供しています。

  • AJAX Framework Commands はページにダイナミックな動作をさせるために、サーバー側で使用することができます。実際には、コールバック関数(#ajax['callback'])はレンダー配列もしくは HTML 文字列の代わりに、これらのコマンドの配列を返すことが出来ます。これらはシンプルなフォーム API による操作を超えた、ダイナミックなページ生成のための関数です。例えば Views モジュールは、そのユーザーインターフェイスにこれらを多く使用します。
  • コールバック関数(#ajax['callback'])はフォームの一部だけでなく、レンダー配列やHTML文字列そのものを返すこともできます。
  • 「置き換え」のメソッドはデフォルトであり最も一般的ですが、「前に挿入」、「後に挿入」など、コールバック関数(#ajax['callback'])の設定により、他のものでも可能です。
  • ajax_form_callback() 関数は独自の関数に変更することができます。その場合は ajax_form_callback() を参考にするとよいでしょう。#ajax['path'] をデフォルトの 'system/ajax' から独自のものに変更するには、メニューエントリーを hook_menu() を使って独自の AJAX コールバック関数に割り当てます。

その他のリソース

  • AJAX対応のドロップダウン、ほかJavaScript未対応ブラウザでの対処など、ここで説明したことは Examples module で提供されています。
  • AJAX Framework ドキュメンテーション、Form API Reference のディスカッションもご覧ください。
コア: 
Drupal7