Formulir multistep / penyihir

10

Saya mencoba membuat formulir multistep / penyihir untuk Drupal 8.

  • Pengguna mengisi nama depan, bidang nama belakang
  • Klik pada tombol selanjutnya
  • Mengisi lebih banyak informasi
  • Klik pada tombol kirim

Saat ini ada banyak sumber daya yang ditujukan untuk tahapan atau bentuk wizard untuk Drupal 7 seperti ini satu dan ini .

Di sisi lain, saya mengalami beberapa kesulitan mencari tahu yang merupakan cara "Drupal" untuk membuat bentuk Drupal 8 multistep / wizard.

Saya melakukan riset dan menemukan beberapa pendekatan:

  • Menyimpan nilai dengan sistem konfigurasi baru
  • Gunakan antarmuka bentuk wizard ( belum dalam inti )
  • Simpan nilai dengan objek sesi drupal (tidak yakin apakah itu ada atau tidak)

Apakah pendekatan yang valid untuk Drupal 8?

chrisjlee
sumber

Jawaban:

12

Cara termudah untuk melakukan ini adalah dengan menggunakan $ form_state. Dalam metode formBuild () Anda, Anda memiliki if / else atau beralih berdasarkan sesuatu seperti $form_state['step']dan menampilkan elemen formulir yang berbeda. Kemudian Anda memiliki hal yang sama dalam mengirimkan panggilan balik atau memiliki beberapa panggilan balik pengiriman, yang melakukan sesuatu pada objek di $ form_state yang sedang Anda bangun, ubah langkahnya dan tetapkan $form_state['rebuild']bendera ke TRUE.

Ada beberapa kelemahan dari pendekatan itu, itulah sebabnya (di antara alasan lain) wisaya bentuk ctools dibuat. Ini bisa menjadi rumit jika Anda memiliki beberapa langkah dan harus mendefinisikan semua itu dalam satu fungsi bentuk / kelas dan semuanya terjadi dalam permintaan POST.

Apa yang dilakukan wizard formulir ctools adalah mengelompokkan beberapa, memisahkan formulir bersama dan mengontrol navigasi dari satu ke yang lain. Anda juga menggunakan cache objek ctools untuk menyimpan objek Anda alih-alih $ form_state, karena itu tidak lagi dibagikan di seluruh formulir Anda.

Meskipun sistem itu belum ada, cache objek ctools telah dipindahkan ke 8.x dan sekarang disebut pengguna tempstore, tersedia sebagai layanan: \Drupal::service('user.private_tempstore')(sebelum 8.0-beta8 dipanggil user.tempstore). Ini adalah lapisan di atas toko nilai kunci kedaluwarsa yang memperkenalkan kepemilikan data yang disimpan di sana. Jadi inilah yang memberdayakan pesan terkenal dalam tampilan bahwa pengguna yang berbeda saat ini mengedit tampilan itu dan dikunci karena alasan itu. Keuntungan lain daripada menggunakan $ _SESSION untuk itu adalah bahwa data Anda hanya harus dimuat saat dibutuhkan, ketika Anda mengedit 3 tampilan, kemudian menggunakan $ _SESSION akan berarti bahwa Anda harus memuat dan membawanya sekitar pada setiap permintaan satu halaman.

Jika Anda tidak membutuhkannya, maka Anda bisa mengandalkan sesi atau juga langsung meletakkannya di toko nilai kunci yang diharapkan ($ form_state disimpan di sana sekarang juga, bukan cache pseudo seperti pada 7.x).

Namun sistem konfigurasi tidak cocok. Itu tidak dimaksudkan untuk konten per pengguna (atau konten sama sekali) karena tidak benar-benar skala untuk menyimpan ribuan atau puluhan ribu catatan dan mungkin membuat beberapa asumsi untuk memuat semua yang mungkin diperlukan pada permintaan halaman tertentu ( belum, tetapi ada masalah untuk mewujudkannya)

Berdir
sumber
Satu lagi pertanyaan tentang jawaban Anda. Ini mungkin pertanyaan konyol: Apakah \ Drupal :: service ('user.tempstore') juga tersedia untuk pengguna anonim?
chrisjlee
Ya, itu kembali ke id sesi untuk pengguna anoymous. Lihat api.drupal.org/api/drupal/…
Berdir
4

Biasanya Anda bisa menyimpan nilai formulir di antara langkah-langkah menggunakan cache objek cTools (mirip dengan formulir Multistep di Drupal 7 ), atau melalui $form_state(sesuai tutorial ini ).

Di Drupal 8 Anda bisa mewarisi FormBasekelas untuk membuat kelas multistep baru.

Dalam artikel Cara Membangun Formulir multi-langkah di Drupal 8 Anda bisa menemukan cara sederhana untuk membuat formulir multistep di Drupal 8.

Pertama-tama, Anda harus membuat kelas dasar yang akan bertugas menyuntikkan dependensi yang diperlukan.

Kami akan mengelompokkan semua kelas formulir bersama dan menempatkannya di dalam folder baru bernama Multistepterletak di dalam Formdirektori plugin modul demo kami. Ini murni untuk memiliki struktur yang bersih dan dapat dengan cepat mengetahui bentuk mana yang merupakan bagian dari proses formulir multi-langkah kami.

Berikut adalah kode demo (untuk MultistepFormBase.phpfile):

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

Kemudian Anda bisa membuat kelas bentuk aktual di dalam file bernama 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');
  }
}

Dalam buildForm()metode ini kita mendefinisikan dua elemen bentuk boneka kami. Perhatikan bahwa kami mengambil definisi formulir yang ada dari kelas induk terlebih dahulu. Nilai default untuk bidang ini ditetapkan sebagai nilai yang ditemukan di toko untuk kunci tersebut (sehingga pengguna dapat melihat nilai yang mereka isi pada langkah ini jika mereka kembali ke sana). Akhirnya, kami mengubah nilai tombol tindakan ke Berikutnya (untuk menunjukkan bahwa formulir ini bukan yang terakhir).

Dalam submitForm()metode kami menyimpan nilai yang dikirimkan ke toko dan kemudian mengarahkan ulang ke bentuk kedua (yang dapat ditemukan di rute demo.multistep_two). Ingatlah bahwa kami tidak melakukan validasi apa pun di sini untuk menjaga kode tetap ringan. Tetapi sebagian besar kasus penggunaan akan meminta beberapa validasi input.

Dan perbarui file perutean Anda di modul 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'

Akhirnya, buat formulir kedua ( 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');
  }
}

Di dalam submitForm()metode kami sekali lagi menyimpan nilai-nilai ke toko dan tunduk pada kelas induk untuk bertahan data ini dengan cara apa pun yang menurutnya cocok. Kami kemudian mengarahkan ke halaman apa pun yang kami inginkan (rute yang kami gunakan di sini adalah dummy).

Kami sekarang harus memiliki formulir multistep yang berfungsi yang menggunakan PrivateTempStoreuntuk menjaga data tersedia di beberapa permintaan. Jika kita membutuhkan lebih banyak langkah, yang harus kita lakukan adalah membuat beberapa formulir lagi, menambahkannya di antara yang sudah ada dan membuat beberapa penyesuaian.

kenorb
sumber
1

Panduan multistep yang telah Anda sebutkan, sudah terintegrasi dengan CTools, lihat: Dukungan Wizard untuk 8.x-3.x , jadi Anda dapat mempertimbangkan untuk memperluasnya your_module.services.yml, misalnya

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

kemudian rentangkan kelas di 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 {

}
kenorb
sumber
Anda tahu contoh yang bisa membantu memahami cara menggunakan wizard multistep CTools?
Duncanmoo
1
@Duncanmoo Saya tidak punya, tetapi merasa bebas untuk mengajukan pertanyaan lain dengan masalah spesifik yang Anda alami, atau mencari di Tests/Wizard/CToolsWizard*file di mana Anda dapat menemukan beberapa tes (misalnya testWizardSteps).
kenorb