DrupalのAHAHフォーム作成

DrupalのAHAHフォーム作成

Posted by Kohei Hayashi on 木, 03/04/2010 - 14:59

AHAH (Asynchronous HTML and HTTP)はAJAXの一種ですが、DrupalでAHAHと言うとほとんどの場合Form APIで作成したフォームの要素をリロード無しで更新することを指します。一度把握してしまえば簡単なので、まとめてみました。

まず一番重要なことは、Drupalのフォームはサーバー側にキャッシュされ、いかなる時でもサーバー側のフォームとクライアント側に表示されているフォームは同一でなければならない、ということです。この法則を守らないでクライアント側のフォームだけjavascriptで更新しようとすると、不正にフォームが変更されたとみなされ、送信の際にvalidationに引っかかるので注意が必要です。

以上の点を理解した上で、実際のプログラミングの手順は以下のようになります。
1. Form APIでフォームを定義する際にどの要素でAHAHを使用するかを#ahahキーで指定します。また、ブラウザーからの送信内容によってフォーム定義が動的に変化するようにしておきます。

2. Drupalがフォームを作成、サーバーにキャッシュし、ブラウザーに表示します。

3. 1で指定した要素(例:セレクトボタン)でAHAHによるHTTPリクエストを送信し、Drupalのメニュー関数を通じてメニューコールバックにアクセスします。

4. サーバーでキャッシュされていたフォームを呼び出します。

5. drupal_process_formでフォームをプロセスします。下記の例では使用しませんが、この際にvalidateハンドラーとsubmitハンドラーもコールバックされるので、$form_stateの内容を変更することが可能です。

6. drupal_rebuild_formによりフォームを再構築します。この際、$_POSTは破棄されるので、submitハンドラーが再びコールバックされることはありません。また、フォーム再構築の際は$form_stateによりフォーム定義が変化し、
再構築されたフォームでサーバーのキャッシュが更新されます。

7. この時点ではサーバーのキャッシュとクライアントに表示されているフォームが同一でないため、drupal_renderで差異のある部分のみをレンダリングします。

8. ブラウザーがデータをjsonで受け取り、フォームの必要な部分のみが自動的に更新されます。これで、再びサーバーキャッシュ=クライアント側のフォーム、という公式が成り立ちます。

例として、料理の種類を選ぶとメニューが変わるというフォームを作成してみましたので、ご参考まで。

<?php

/**
* Implementation of hook_menu().
*/
function ahahtest_menu() {
  $items['ahahtest'] = array(
    'title' => 'AHAH test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('ahahtest_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

  //3. AHAHによるHTTPリクエスト用のマッピング
  $items['ahahtest/js'] = array(
    'title' => 'AHAH js callback',
    'page callback' => 'ahahtest_js',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}

/**
* フォーム定義。
*/
function ahahtest_form($form_state) {
  $cuisines = array(
    'chinese' => '中華',
    'italian' => 'イタリアン',
    'american' => 'アメリカン',
  );

  //1. AHAHをバインドする
  $form['cuisine'] = array(
    '#type' => 'select',
    '#title' => '料理のカテゴリ',
    '#options' => $cuisines,
    '#ahah' => array(
      'path' => 'ahahtest/js',
      'wrapper' => 'ahah-menus',
    ),
  );

  //デフォルトのメニュー
  $menus = array('ラーメン', '餃子', '麻婆豆腐');

  //AHAHでフォーム再構築する際に使用。
  if (isset($form_state['values']['cuisine'])) {
    if ($form_state['values']['cuisine'] == 'italian') {
      $menus = array('ピザ', 'パスタ', 'リゾット');
    }
    elseif ($form_state['values']['cuisine'] == 'american') {
      $menus = array('ハンバーガー', 'フライドポテト', 'ステーキ');
    }
  }

  //AHAHのターゲットID。wrapperの内側にあるHTMLが更新されます。
  $form['wrapper'] = array(
    '#prefix' => '<div id="ahah-menus" style="clear:both">',
    '#suffix' => '</div>',
  );
  $form['wrapper']['menus'] = array(
    '#title' => 'メニュー',
    '#type' => 'select',
    '#options' => $menus,
  );

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

  return $form;
}

/**
* AHAH menu callback.
*/
function ahahtest_js() {
  $form_state = array('storage' => NULL, 'submitted' => FALSE);
  $form_build_id = $_POST['form_build_id'];
  //4. サーバーキャッシュからフォーム定義を呼び出す。
  $form = form_get_cache($form_build_id, $form_state);

  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form_state['post'] = $form['#post'] = $_POST;
  $form['#programmed'] = $form['#redirect'] = FALSE;

  //5. フォームをプロセス。
  drupal_process_form($form_id, $form, $form_state);

  //6. フォームを再構築。$form['wrapper']['menus']が送信内容に応じて変化。
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);

  //7. 変化した部分のみ抽出
  $output = drupal_render($form['wrapper']['menus']);

  //8. jsonでブラウザーにデータを送信。
  drupal_json(array('status' => True, 'data' => $output));
}

コンテンツの配信