マルチステップ/ウィザードフォーム


10

Drupal 8のマルチステップ/ウィザードフォームを作成しようとしています。

  • ユーザーは名、姓のフィールドに入力します
  • 次のボタンをクリックします
  • 詳細情報を記入
  • 送信ボタンをクリックする

このようにDrupalの7のための多段階またはウィザード形式に専念多くのリソース現在ありません1、これは

一方、Drupal 8のマルチステップ/ウィザードフォームを作成する「Drupal」の方法を理解するのに問題がありました。

私はいくつかの調査を行い、いくつかのアプローチがあると考えました:

Drupal 8に有効なアプローチはありますか?

回答:


12

これを行う最も簡単な方法は、$ form_stateを使用することです。formBuild()メソッドには、に基づいてif / elseまたはswitchが$form_state['step']あり、さまざまなフォーム要素を表示します。次に、送信コールバックに同じか、複数の送信コールバックがあり、作成中の$ form_stateのオブジェクトに対して何かを実行し、ステップを変更して$form_state['rebuild']フラグをTRUEに設定します。

このアプローチにはいくつかの欠点があります。そのため、(他の理由とともに)ctoolsフォームウィザードが作成されました。複数のステップがあり、すべてを単一のフォームの関数/クラスで定義する必要があり、すべてがPOSTリクエストで発生する場合、複雑になる可能性があります。

ctoolsフォームウィザードが行うことは、複数の個別のフォームをグループ化し、一方から他方へのナビゲーションを制御することです。また、フォーム間で共有されなくなったため、$ form_stateの代わりにctoolsオブジェクトキャッシュを使用してオブジェクトを保存します。

そのシステムはまだ存在しませんが、ctoolsオブジェクトキャッシュは8.xに移植され、現在はユーザーテンプストアと呼ばれ、サービスとして利用可能です\Drupal::service('user.private_tempstore')8.0-beta8より前はと呼ばれていましたuser.tempstore)。これは、そこに格納されたデータの所有権を導入する、有効期限のあるキーバリューストアの上のレイヤーです。したがって、これが、別のユーザーが現在そのビューを編集していて、その理由でロックされているビューの既知のメッセージを強化するものです。そのために$ _SESSIONを使用するもう1つの利点は、3つのビューを編集しているときに必要なときにのみデータをロードする必要があることです。$ _ SESSIONを使用すると、1つのページリクエストごとにロードして持ち運ぶ必要があります。

それが必要ない場合は、セッションに依存するか、有効期限のあるキー値ストアに直接置くこともできます($ form_stateも7.xのような疑似キャッシュではなく、ここに格納されます)。

ただし、構成システムは適していません。これは実際には数千または数万のレコードを格納するように拡張されておらず、特定のページリクエストで必要になる可能性のあるすべてを事前にロードすることを想定しているため、ユーザーごとのコンテンツ(またはコンテンツ)向けではありません(まだですが、それを実現するには問題があります)


あなたの答えについてもう一つ質問。これはばかげた質問かもしれません:\ Drupal :: service( 'user.tempstore')は匿名ユーザーでも利用できますか?
chrisjlee 2013年

はい、匿名ユーザーのセッションIDにフォールバックします。参照してくださいapi.drupal.org/api/drupal/...
Berdir

4

通常、cToolsオブジェクトキャッシュ(Drupal 7のマルチステップフォームと同様)を使用して、または$form_state(このチュートリアルのように)を使用して、ステップ間でフォーム値を保存できます。

Drupal 8ではFormBase、クラスを継承して新しいマルチステップクラスを作成できます。

Drupal 8でマルチステップフォームを作成する方法の記事では、Drupal 8でマルチステップフォームを作成する簡単な方法を見つけることができます。

まず、必要な依存関係の注入を担当する基本クラスを作成する必要があります。

すべてのフォームクラスをグループ化し、デモモジュールのプラグインディレクトリMultistep内にあるという新しいフォルダー内に配置しFormます。これは、純粋にクリーンな構造を持ち、どのフォームがマルチステップフォームプロセスの一部であるかをすばやく識別できるようにするためのものです。

デモコードは次のMultistepFormBase.phpとおりです(ファイル用):

/**
 * @file
 * Contains \Drupal\demo\Form\Multistep\MultistepFormBase.
 */

namespace Drupal\demo\Form\Multistep;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\SessionManagerInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;

abstract class MultistepFormBase extends FormBase {

  /**
   * @var \Drupal\user\PrivateTempStoreFactory
   */
  protected $tempStoreFactory;

  /**
   * @var \Drupal\Core\Session\SessionManagerInterface
   */
  private $sessionManager;

  /**
   * @var \Drupal\Core\Session\AccountInterface
   */
  private $currentUser;

  /**
   * @var \Drupal\user\PrivateTempStore
   */
  protected $store;

  /**
   * Constructs a \Drupal\demo\Form\Multistep\MultistepFormBase.
   *
   * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
   * @param \Drupal\Core\Session\SessionManagerInterface $session_manager
   * @param \Drupal\Core\Session\AccountInterface $current_user
   */
  public function __construct(PrivateTempStoreFactory $temp_store_factory, SessionManagerInterface $session_manager, AccountInterface $current_user) {
    $this->tempStoreFactory = $temp_store_factory;
    $this->sessionManager = $session_manager;
    $this->currentUser = $current_user;

    $this->store = $this->tempStoreFactory->get('multistep_data');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('user.private_tempstore'),
      $container->get('session_manager'),
      $container->get('current_user')
    );
  }

  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    // Start a manual session for anonymous users.
    if ($this->currentUser->isAnonymous() && !isset($_SESSION['multistep_form_holds_session'])) {
      $_SESSION['multistep_form_holds_session'] = true;
      $this->sessionManager->start();
    }

    $form = array();
    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
      '#button_type' => 'primary',
      '#weight' => 10,
    );

    return $form;
  }

  /**
   * Saves the data from the multistep form.
   */
  protected function saveData() {
    // Logic for saving data goes here...
    $this->deleteStore();
    drupal_set_message($this->t('The form has been saved.'));

  }

  /**
   * Helper method that removes all the keys from the store collection used for
   * the multistep form.
   */
  protected function deleteStore() {
    $keys = ['name', 'email', 'age', 'location'];
    foreach ($keys as $key) {
      $this->store->delete($key);
    }
  }
}

次に、次のファイル内に実際のフォームクラスを作成できますMultistepOneForm.php

/**
 * @file
 * Contains \Drupal\demo\Form\Multistep\MultistepOneForm.
 */

namespace Drupal\demo\Form\Multistep;

use Drupal\Core\Form\FormStateInterface;

class MultistepOneForm extends MultistepFormBase {

  /**
   * {@inheritdoc}.
   */
  public function getFormId() {
    return 'multistep_form_one';
  }

  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    $form = parent::buildForm($form, $form_state);

    $form['name'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Your name'),
      '#default_value' => $this->store->get('name') ? $this->store->get('name') : '',
    );

    $form['email'] = array(
      '#type' => 'email',
      '#title' => $this->t('Your email address'),
      '#default_value' => $this->store->get('email') ? $this->store->get('email') : '',
    );

    $form['actions']['submit']['#value'] = $this->t('Next');
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->store->set('email', $form_state->getValue('email'));
    $this->store->set('name', $form_state->getValue('name'));
    $form_state->setRedirect('demo.multistep_two');
  }
}

このbuildForm()メソッドでは、2つのダミーフォーム要素を定義しています。最初に親クラスから既存のフォーム定義を取得していることに注意してください。これらのフィールドのデフォルト値は、これらのキーのストアにある値として設定されます(ユーザーがこのステップで入力した値を、戻って来た場合に表示できるようにするため)。最後に、アクションボタンの値を[次へ]に変更します(このフォームが最後のフォームではないことを示します)。

このsubmitForm()メソッドでは、送信された値をストアに保存してから、2番目のフォーム(routeにありますdemo.multistep_two)にリダイレクトします。ここでは、コードを軽量化するための検証は一切行っていません。ただし、ほとんどの使用例では、入力の検証が必要になります。

そして、デモモジュール(demo.routing.yml)のルーティングファイルを更新します。

demo.multistep_one:
  path: '/demo/multistep-one'
  defaults:
    _form: '\Drupal\demo\Form\Multistep\MultistepOneForm'
    _title: 'First form'
  requirements:
    _permission: 'access content'
demo.multistep_two:
  path: '/demo/multistep-two'
  defaults:
    _form: '\Drupal\demo\Form\Multistep\MultistepTwoForm'
    _title: 'Second form'
  requirements:
    _permission: 'access content'

最後に、2番目のフォーム(MultistepTwoForm)を作成します。

/**
 * @file
 * Contains \Drupal\demo\Form\Multistep\MultistepTwoForm.
 */

namespace Drupal\demo\Form\Multistep;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;

class MultistepTwoForm extends MultistepFormBase {

  /**
   * {@inheritdoc}.
   */
  public function getFormId() {
    return 'multistep_form_two';
  }

  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    $form = parent::buildForm($form, $form_state);

    $form['age'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Your age'),
      '#default_value' => $this->store->get('age') ? $this->store->get('age') : '',
    );

    $form['location'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Your location'),
      '#default_value' => $this->store->get('location') ? $this->store->get('location') : '',
    );

    $form['actions']['previous'] = array(
      '#type' => 'link',
      '#title' => $this->t('Previous'),
      '#attributes' => array(
        'class' => array('button'),
      ),
      '#weight' => 0,
      '#url' => Url::fromRoute('demo.multistep_one'),
    );

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->store->set('age', $form_state->getValue('age'));
    $this->store->set('location', $form_state->getValue('location'));

    // Save the data
    parent::saveData();
    $form_state->setRedirect('some_route');
  }
}

submitForm()メソッド内で、値をストアに再度保存し、親クラスに委ねて、適切と思われる方法でこのデータを永続化します。次に、必要なページにリダイレクトします(ここで使用するルートはダミーです)。

これで、をPrivateTempStore使用して複数のリクエストにわたってデータを利用できるようにする、機能するマルチステップフォームができました。さらに手順が必要な場合は、さらにいくつかのフォームを作成し、既存のフォームの間に追加して、いくつかの調整を行うだけです。


1

多段階のウィザードあなたが言及したことは、それはすでにCToolsと統合されて、次を参照してください。8.xの-3.xのウィザードのサポートを、あなたはそれを拡張検討することができるので、your_module.services.yml例えば、

services:
  ctools.wizard.form:
    class: Drupal\MyModuleMultistep\Controller\MyWizardForm

次にクラスを拡張しsrc/Controller/MyWizardForm.phpます:

<?php

/**
 * @file
 * Contains \Drupal\MyModuleMultistep\Controller\MyWizardForm
 */

namespace Drupal\MyModuleMultistep\Controller;

/**
 * Wrapping controller for wizard forms that serve as the main page body.
 */
class MyWizardForm extends WizardFormController {

}

CToolsマルチステップウィザードの使用方法を理解するのに役立つ例を知っていますか?
Duncanmoo 2017

1
@Duncanmoo私にはありませんが、あなたが抱えている特定の問題について別の質問をTests/Wizard/CToolsWizard*するか、いくつかのテストを見つけることができるファイル(例:)を自由に見てくださいtestWizardSteps
kenorb 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.