Biểu mẫu nhiều bước / thuật sĩ


10

Tôi đang cố gắng tạo một biểu mẫu nhiều hướng dẫn / thuật sĩ cho Drupal 8.

  • Người dùng điền vào tên, trường họ
  • Nhấp vào nút tiếp theo
  • Điền thêm thông tin
  • Nhấp vào nút gửi

Hiện tại có nhiều nguồn lực dành cho nhiều bước hoặc các hình thức wizard cho Drupal 7 như thế này mộtnày .

Mặt khác, tôi đã gặp một số khó khăn khi tìm ra đó là cách "Drupal" để tạo các biểu mẫu đa hướng / thuật sĩ Drupal 8.

Tôi đã thực hiện một số nghiên cứu và tìm ra có một số cách tiếp cận:

  • Lưu trữ giá trị với hệ thống cấu hình mới
  • Sử dụng giao diện biểu mẫu thuật sĩ ( chưa có trong lõi )
  • Lưu trữ giá trị với đối tượng phiên drupal (không chắc có tồn tại hay không)

Là những cách tiếp cận hợp lệ cho Drupal 8?

Câu trả lời:


12

Cách dễ nhất để làm điều này là sử dụng $ form_state. Trong phương thức formBuild () của bạn, bạn có if / other hoặc switch dựa trên một cái gì đó giống như $form_state['step']và hiển thị các thành phần biểu mẫu khác nhau. Sau đó, bạn có cùng một lần gọi lại hoặc có nhiều cuộc gọi lại, điều đó làm một cái gì đó cho một đối tượng trong $ form_state mà bạn đang xây dựng, thay đổi bước và đặt $form_state['rebuild']cờ thành TRUE.

Có một vài nhược điểm của cách tiếp cận đó, đó là lý do tại sao (trong số các lý do khác) trình hướng dẫn biểu mẫu ctools được tạo. Nó có thể trở nên phức tạp nếu bạn có nhiều bước và phải xác định tất cả các bước đó trong một hàm / lớp biểu mẫu duy nhất và mọi thứ xảy ra trong các yêu cầu POST.

Những gì trình hướng dẫn biểu mẫu ctools làm là nhóm nhiều, các biểu mẫu riêng biệt với nhau và điều khiển điều hướng từ cái này sang cái khác. Bạn cũng sử dụng bộ đệm đối tượng ctools để lưu trữ đối tượng của mình thay vì $ form_state, vì điều đó không còn được chia sẻ trên các biểu mẫu của bạn.

Mặc dù hệ thống đó chưa tồn tại, bộ đệm đối tượng ctools đã được chuyển sang 8.x và hiện được gọi là tempstore của người dùng, có sẵn dưới dạng dịch vụ: \Drupal::service('user.private_tempstore')(trước 8.0-beta8 được gọi user.tempstore). Đây là một lớp trên cùng của kho lưu trữ khóa có thể mở rộng giới thiệu quyền sở hữu dữ liệu được lưu trữ trong đó. Vì vậy, đây là những gì cung cấp thông điệp nổi tiếng trong các chế độ xem mà một người dùng khác hiện đang chỉnh sửa chế độ xem đó và nó bị khóa vì lý do đó. Một lợi thế khác khi sử dụng $ _SESSION cho điều đó là dữ liệu của bạn chỉ phải được tải khi cần, khi bạn chỉnh sửa 3 lượt xem, sau đó sử dụng $ _SESSION có nghĩa là bạn phải tải và mang theo chúng trên mỗi yêu cầu trang.

Nếu bạn không cần điều đó, thì bạn có thể dựa vào phiên hoặc trực tiếp đặt nó vào kho lưu trữ khóa có thể hết hạn ($ form_state cũng được lưu trữ ở đó, không phải là bộ đệm giả như trong 7.x).

Tuy nhiên, hệ thống cấu hình không phù hợp. Điều đó không có nghĩa đối với nội dung trên mỗi người dùng (hoặc nội dung) vì nó không thực sự mở rộng để lưu trữ hàng ngàn hoặc hàng chục nghìn bản ghi và có thể đưa ra một số giả định để tải trước mọi thứ nó có thể cần cho một yêu cầu trang nhất định ( chưa, nhưng có một vấn đề để thực hiện điều đó)


Thêm một câu hỏi về câu trả lời của bạn. Đây có thể là một câu hỏi ngớ ngẩn: Có phải \ Drupal :: service ('user.tempstore') cũng có sẵn cho người dùng ẩn danh không?
chrisjlee

Có, nó rơi trở lại id phiên cho người dùng anoymous. Xem api.drupal.org/api/drupal/ khăn
Berdir

4

Thông thường, bạn có thể lưu trữ giá trị biểu mẫu giữa các bước bằng cách sử dụng bộ đệm đối tượng cTools (tương tự như biểu mẫu Multistep trong Drupal 7 ) hoặc thông qua $form_state(theo hướng dẫn này ).

Trong Drupal 8, bạn có thể kế thừa FormBaselớp để tạo lớp đa lớp mới.

Trong bài viết Cách xây dựng biểu mẫu nhiều bước trong Drupal 8, bạn có thể tìm thấy một cách đơn giản để tạo biểu mẫu nhiều bước trong Drupal 8.

Trước hết, bạn sẽ cần tạo lớp cơ sở chịu trách nhiệm tiêm các phụ thuộc cần thiết.

Chúng tôi sẽ nhóm tất cả các lớp biểu mẫu lại với nhau và đặt chúng trong một thư mục mới có tên Multistepnằm trong thư mục Formplugin của mô-đun demo. Điều này hoàn toàn là để có một cấu trúc sạch và có thể nhanh chóng cho biết các biểu mẫu nào là một phần của quy trình biểu mẫu nhiều bước của chúng tôi.

Đây là mã demo (cho MultistepFormBase.phptệp):

/**
 * @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);
    }
  }
}

Sau đó, bạn có thể tạo lớp biểu mẫu thực tế bên trong một tệp có tên 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');
  }
}

Trong buildForm()phương pháp, chúng tôi đang xác định hai yếu tố hình thức giả của chúng tôi. Xin lưu ý rằng chúng tôi đang truy xuất định nghĩa biểu mẫu hiện có từ lớp cha trước. Các giá trị mặc định cho các trường này được đặt làm giá trị tìm thấy trong cửa hàng cho các khóa đó (để người dùng có thể thấy các giá trị họ đã điền ở bước này nếu họ quay lại với nó). Cuối cùng, chúng tôi sẽ thay đổi giá trị của nút hành động thành Tiếp theo (để chỉ ra rằng hình thức này không phải là hình thức cuối cùng).

Trong submitForm()phương thức, chúng tôi lưu các giá trị được gửi vào cửa hàng và sau đó chuyển hướng đến dạng thứ hai (có thể tìm thấy tại tuyến đường demo.multistep_two). Hãy nhớ rằng chúng tôi không thực hiện bất kỳ loại xác nhận nào ở đây để giữ cho mã sáng. Nhưng hầu hết các trường hợp sử dụng sẽ gọi cho một số xác nhận đầu vào.

Và cập nhật tệp định tuyến của bạn trong mô-đun demo ( 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'

Cuối cùng, tạo dạng thứ hai ( 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');
  }
}

Bên trong submitForm()phương thức, chúng ta lại lưu các giá trị vào cửa hàng và trì hoãn lớp cha để duy trì dữ liệu này theo bất kỳ cách nào nó thấy phù hợp. Sau đó chúng tôi chuyển hướng đến bất kỳ trang nào chúng tôi muốn (tuyến đường chúng tôi sử dụng ở đây là một trang giả).

Bây giờ chúng ta nên có một biểu mẫu nhiều bước hoạt động sử dụng PrivateTempStoređể giữ dữ liệu có sẵn trên nhiều yêu cầu. Nếu chúng ta cần nhiều bước hơn, tất cả những gì chúng ta phải làm là tạo thêm một số biểu mẫu, thêm chúng vào giữa những cái hiện có và thực hiện một vài điều chỉnh.


1

Các thuật sĩ nhiều bước mà bạn đã đề cập, nó đã tích hợp với CTools, xem: Hướng dẫn Hỗ trợ cho 8.x-3.x , vì vậy bạn có thể xem xét gia hạn nó trong your_module.services.yml, ví dụ:

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

sau đó mở rộng lớp trong 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 {

}

Bạn có biết một ví dụ có thể giúp hiểu cách sử dụng thuật sĩ đa cấp CTools không?
Duncanmoo

1
@Duncanmoo Tôi không có, nhưng cứ thoải mái đặt một câu hỏi khác với vấn đề cụ thể mà bạn đang gặp phải hoặc tìm trong Tests/Wizard/CToolsWizard*các tệp mà bạn có thể tìm thấy một số bài kiểm tra (ví dụ testWizardSteps).
kenorb
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.