drupal8 表单_如何在Drupal 8中构建多步骤表单

drupal8 表单

In this article, we are going to look at building a multistep form in Drupal 8. For brevity, the form will have only two steps in the shape of two completely separate forms. To persist values across these steps, we will use functionality provided by Drupal’s core for storing temporary and private data across multiple requests.

在本文中,我们将研究在Drupal 8中构建多步骤表单。为简便起见,该表单只有两个步骤,处于两个完全独立的表单的形式。 为了在这些步骤中保持价值,我们将使用Drupal核心提供的功能来跨多个请求存储临时和私有数据。

Drupal 8 logo

In Drupal 7, a similar approach can be achieved using the cTools object cache. Alternatively, there is the option of persisting data through the $form_state array as illustrated in this tutorial.

在Drupal 7中,可以使用cTools对象缓存实现类似的方法。 或者,可以选择通过$form_state数组持久存储数据,如本教程中所示

The code we write in this article can be found in this repository alongside much of the Drupal 8 work we’ve been doing so far. We will be dealing with forms quite a lot so I do recommend checking out one of the previous articles on Drupal 8 in which we talk about forms.

我们在本文中编写的代码可以与我们到目前为止所做的许多Drupal 8工作一起在该存储库中找到。 我们将大量处理表单,因此,我建议您查阅有关Drupal 8的前一篇文章中有关表单的文章。

计划 (The plan)

As I mentioned above, our multistep form will consist of two independent forms with two simple elements each. Users will be able to fill in the first one and move to the second form where they can either go back to the previous step or fill it in and press submit. While navigating between the different steps, the previously submitted values are stored and used to pre-populate the form fields. If the last form is submitted, however, the data gets processed (not covered in this article) and cleared from the temporary storage.

如上所述,我们的多步骤表单将包含两个独立的表单,每个表单具有两个简单的元素。 用户将能够填写第一个表格并移至第二个表格,在这里他们可以返回上一步或填写表格并按提交。 在不同步骤之间导航时,先前提交的值将被存储并用于预填充表单字段。 但是,如果提交了最后一个表单,则数据将被处理(本文未介绍)并从临时存储中清除。

Technically, both of these forms will inherit common functionality from an abstract form class we will call MultistepFormBase. This class will be in charge of injecting the necessary dependencies, scaffolding the form, processing the end result and anything else that is needed and is common to both.

从技术上讲,这两种形式都将从一个抽象的表单类(称为MultistepFormBase继承通用功能。 此类将负责注入必要的依赖项,对表单进行脚手架,处理最终结果以及其他所有需要且共同的东西。

We will group all the form classes together and place them inside a new folder called Multistep located within the Form plugin directory of our demo module (next to the old DemoForm). This is purely for having a clean structure and being able to quickly tell which forms are part of our multistep form process.

我们将所有组的形式一起上课,并放置在一个名为新文件夹内Multistep位于内Form插件我们的目录demo模块(旁边的老DemoForm )。 这纯粹是为了拥有一个干净的结构并能够快速分辨出哪些表单是我们多步骤表单过程的一部分。

代码 (The code)

We will start with the form base class. I will explain what is going on here after we see the code.

我们将从表单基类开始。 看到代码后,我将解释这里发生的情况。

MultistepFormBase.php:

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);
    }
  }
}

Our abstract form class extends from the default Drupal FormBase class so that we can use some of the functionality made available by it and the traits it uses. We are using dependency injection to inject some of the needed services:

我们的抽象表单类是从默认的Drupal FormBase类扩展而来的,因此我们可以使用它提供的某些功能以及它使用的特征。 我们正在使用依赖注入来注入一些所需的服务:

  • PrivateTempStoreFactory gives us a temporary store that is private to the current user (PrivateTempStore). We will keep all the submitted data from the form steps in this store. In the constructor, we are also immediately saving the store attribute which contains a reference to the multistep_data key/value collection we will use for this process. The get() method on the factory either creates the store if it doesn’t exist or retrieves it from the storage.

    PrivateTempStoreFactory为我们提供了一个临时存储,该存储对当前用户是私有的( PrivateTempStore )。 我们将从表单步骤中保存所有提交的数据。 在构造函数中,我们还将立即保存store属性,其中包含对将用于此过程的multistep_data键/值集合的引用。 工厂中的get()方法创建存储(如果不存在)或从存储中检索它。

  • The SessionManager allows us to start a session for anonymous users.

    SessionManager允许我们为匿名用户启动会话。

  • The CurrentUser allows us to check if the current user is anonymous.

    CurrentUser允许我们检查当前用户是否为匿名用户。

Inside the buildForm() method we do two main things. First, we start a session for anonymous users if one does’t already exist. This is because without a session we cannot pass around temporary data across multiple requests. We use the session manager for this. Second, we create a base submit action button that will be present on all the implementing forms.

buildForm()方法内部,我们做两件事。 首先,如果匿名用户不存在,则为它启动会话。 这是因为没有会话,我们将无法在多个请求之间传递临时数据。 我们为此使用会话管理器。 其次,我们创建一个基本的提交操作按钮,该按钮将出现在所有实施表单上。

The saveData() method is going to be called from one or more of the implementing forms and is responsible with persisting the data from the temporary storage once the multistep process is completed. We won’t be going into the details of this implementation because it depends entirely on your use case (e.g. you can create a configuration entity from each submission). We do, however, handle the removal of all the items in the store once the data has been persisted. Keep in mind though that these types of logic checks should not be performed in the base class. You should defer to a dedicated service class as usual, or use a similar approach.

将通过一种或多种实现形式调用saveData()方法,并负责在完成多步骤过程后持久存储来自临时存储区的数据。 我们将不讨论此实现的细节,因为它完全取决于您的用例(例如,您可以从每个提交中创建一个配置实体)。 但是,一旦数据持久化,我们就会处理商店中所有项目的移除。 请记住,尽管这些逻辑检查类型不应在基类中执行。 您应该照常使用专用的服务类,或使用类似的方法。

Now it’s time for the actual forms that will represent steps in the process. We start with the first class inside a file called MultistepOneForm.php:

现在该是表示流程中各个步骤的实际表格的时候了。 我们从名为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');
  }
}

This form will look something like this:

该表格如下所示:

Drupal 8 Multistep Forms 1

In the buildForm() method we are defining our two dummy form elements. Do notice that we are retrieving the existing form definition from the parent class first. The default values for these fields are set as the values found in the store for those keys (so that users can see the values they filled in at this step if they come back to it). Finally, we are changing the value of the action button to Next (to indicate that this form is not the final one).

buildForm()方法中,我们定义了两个虚拟表单元素。 请注意,我们首先要从父类中检索现有的表单定义。 这些字段的默认值设置为在商店中找到的那些键的值(这样,如果用户返回到该值,则可以看到他们在此步骤中填写的值)。 最后,我们将操作按钮的值更改为“ Next (以表明此表单不是最终表单)。

In the submitForm() method we save the submitted values to the store and then redirect to the second form (which can be found at the route demo.multistep_two). Keep in mind that we are not doing any sort of validation here to keep the code light. But most use cases will call for some input validation.

submitForm()方法中,我们将提交的值保存到商店,然后重定向到第二种形式(可以在路线demo.multistep_two找到)。 请记住,在这里我们不做任何形式的验证以保持代码的简洁。 但是大多数用例都需要进行一些输入验证。

Since we’ve touched upon the issue of routes, let’s update the route file in our demo module and create two new routes for our forms:

由于我们已经讨论了路由问题,因此让我们在demo模块中更新路由文件,并为表单创建两个新的路由:

demo.routing.yml:

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'

For more information about what is going on in this file you can read one of the previous Drupal 8 articles which explain routes as well.

有关此文件中发生的情况的更多信息,您可以阅读以前的Drupal 8文章之一,该文章还介绍了路由。

Finally, we can create our second form (inside a file called MultistepTwoForm):

最后,我们可以创建第二个表单(在名为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');
  }
}

This one will look like this, again very simple:

这看起来像这样,再次非常简单:

Drupal 8 Multistep Forms 1

Again, we are extending from our base class like we did with the first form. This time, however, we have different form elements and we are adding a new action link next to the submit button. This will allow users to navigate back to the first step of the form process.

同样,我们像第一种形式一样从基类扩展。 但是,这次,我们具有不同的表单元素,并且在“提交”按钮旁边添加了一个新的操作链接。 这将允许用户导航回到表单过程的第一步。

Inside the submitForm() method we again save the values to the store and defer to the parent class to persist this data in any way it sees fit. We then redirect to whatever page we want (the route we use here is a dummy one).

submitForm()方法内部,我们再次将值保存到存储中,并推迟到父类以按其认为合适的任何方式持久化此数据。 然后,我们重定向到我们想要的任何页面(此处使用的路由是虚拟的)。

And that is pretty much it. We should now have a working multistep form that uses the PrivateTempStore to keep data available across multiple requests. If we need more steps, all we have to do is create some more forms, add them in between the existing ones and make a couple of adjustments. Of course you can make this much more flexible by not hardcoding the route names in the links and redirects, but I leave that part up to you.

就是这样。 现在,我们应该有一个工作的多步骤表单,该表单使用PrivateTempStore使数据在多个请求中保持可用。 如果需要更多步骤,我们要做的就是创建更多表单,将它们添加到现有表单之间,并进行一些调整。 当然,您可以通过不对链接和重定向中的路由名称进行硬编码来使此操作更加灵活,但是我将这一部分留给您自己解决。

结论 (Conclusion)

In this article, we looked at a simple way to create a multistep form in Drupal 8. You can, of course, build on this approach and create highly complex and dynamic processes that involve not just forms but also other kinds of steps that leverage cross-request data. Thus, the purpose of this article has been as much about multistep forms as it has been about illustrating the power of the PrivateTempStore. And if you, like me, think that the cTools object cache is very powerful in Drupal 7, you’ll be very invested in its Drupal 8 counterpart.

在本文中,我们研究了一种在Drupal 8中创建多步骤表单的简单方法。您当然可以在此方法的基础上,创建高度复杂和动态的过程,该过程不仅涉及表单,而且还涉及利用交叉引用的其他类型的步骤。 -请求数据。 因此,本文的目的与说明多步表单一样,与说明PrivateTempStore 。 并且,如果您像我一样,认为cTools对象缓存在Drupal 7中非常强大,那么您将在其Drupal 8对应产品中投入大量资金。

翻译自: https://www.sitepoint.com/how-to-build-multi-step-forms-in-drupal-8/

drupal8 表单

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值