Answers:
最简单的方法是使用$ form_state。在formBuild()方法中,您有一个if / else或基于类似内容的switch $form_state['step']
并显示不同的表单元素。然后,您要么在您的提交回调中使用相同的回调函数,要么在多个提交回调中对正在构建的$ form_state中的对象执行某项操作,更改步骤并将$form_state['rebuild']
标志设置为TRUE。
该方法有一些缺点,这就是(除其他原因外)创建ctools表单向导的原因。如果您有多个步骤,并且必须以单个形式的函数/类定义所有步骤,并且一切都发生在POST请求中,则操作可能会变得很复杂。
ctools表单向导的作用是将多个单独的表单组合在一起,并控制从一个表单到另一个表单的导航。您还可以使用ctools对象缓存而不是$ form_state来存储对象,因为它不再在表单之间共享。
虽然该系统还不存在,ctools对象缓存已经被移植到8.x和现在被称为用户tempstore,作为服务提供:\Drupal::service('user.private_tempstore')
(之前8.0 beta8叫user.tempstore
)。这是过期密钥值存储之上的一层,该层引入了那里存储数据的所有权。因此,这正是视图中众所周知的消息的力量,该消息是另一个用户当前正在编辑该视图,并且由于该原因而被锁定。相对于使用$ _SESSION的另一个优势是,仅在需要时才需要加载数据,当您编辑3个视图时,然后使用$ _SESSION意味着您必须在每个单页请求中加载并携带它们。
如果不需要它,则可以依赖会话,也可以将其直接放在过期的键值存储中($ form_state现在也存储在此处,而不是7.x中的伪缓存)。
但是,配置系统不是一个很好的选择。这并不是针对每位用户的内容(或根本没有内容),因为它不能真正扩展到存储成千上万的记录,并且可能会做出一些假设来将其可能需要的所有内容预先加载到给定的页面请求中(还没有,但是有一个问题可以实现)
通常,您可以使用cTools对象缓存(类似于Drupal 7中的 Multistep 表单)或通过$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()
方法中,我们定义了两个伪表单元素。请注意,我们首先要从父类中检索现有的表单定义。这些字段的默认值设置为在商店中找到的那些键的值(以便用户返回到此步骤时可以看到他们在此步骤中填写的值)。最后,我们将操作按钮的值更改为“下一步”(以表明此表单不是最终表单)。在该
submitForm()
方法中,我们将提交的值保存到存储中,然后重定向到第二种形式(可以在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'
最后,创建第二个表单(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
来保持跨多个请求的数据可用性。如果需要更多步骤,我们要做的就是创建更多表单,将它们添加到现有表单之间,并进行一些调整。
您已经提到的多步向导已经与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 {
}
Tests/Wizard/CToolsWizard*
文件中查找可以进行一些测试的文件(例如testWizardSteps
)。