Moduli a più fasi / procedura guidata


10

Sto cercando di creare un modulo a più fasi / procedura guidata per Drupal 8.

  • L'utente compila i campi nome, cognome
  • Clic sul pulsante successivo
  • Compila ulteriori informazioni
  • Clic sul pulsante di invio

Al momento non ci sono molte risorse dedicate a più stadi o forme guidata per Drupal 7 come questo uno e questo .

D'altra parte, ho avuto qualche problema a capire quale sia il modo "Drupal" di creare moduli multistep / wizard di Drupal 8.

Ho fatto qualche ricerca e ho pensato che ci fossero diversi approcci:

  • Memorizza i valori con il nuovo sistema di configurazione
  • Utilizzare l'interfaccia del modulo della procedura guidata ( non ancora nel core )
  • Memorizza i valori con l'oggetto sessione drupal (non sono sicuro che esista o meno)

Sono approcci validi per Drupal 8?

Risposte:


12

Il modo più semplice per farlo è usare $ form_state. Nel tuo metodo formBuild (), hai un if / else o switch basato su qualcosa di simile $form_state['step']e visualizza diversi elementi del modulo. Quindi hai lo stesso nel callback di invio o hai più callback di invio, che fanno qualcosa a un oggetto in $ form_state che stai costruendo, modifica il passaggio e imposta il $form_state['rebuild']flag su TRUE.

Ci sono alcuni aspetti negativi di questo approccio, motivo per cui (tra le altre ragioni) è stata creata la procedura guidata del modulo ctools. Può essere complicato se hai più passaggi e devi definire tutto ciò in un unico modulo funzione / classe e tutto accade nelle richieste POST.

Ciò che fa la procedura guidata per i moduli ctools è raggruppare più moduli separati e controllare la navigazione dall'uno all'altro. È inoltre possibile utilizzare la cache degli oggetti ctools per archiviare l'oggetto anziché $ form_state, poiché non è più condiviso tra i moduli.

Sebbene quel sistema non esista ancora, la cache degli oggetti ctools è stata portata su 8.x ed è ora chiamata user tempstore, disponibile come servizio: \Drupal::service('user.private_tempstore')(prima di 8.0-beta8 chiamato user.tempstore). Questo è un livello in cima all'archivio valori-chiave esponibile che introduce la proprietà dei dati memorizzati. Quindi questo è ciò che alimenta il noto messaggio nelle viste che un altro utente sta attualmente modificando quella vista ed è bloccato per questo motivo. Un altro vantaggio rispetto all'utilizzo di $ _SESSION è che i dati devono essere caricati solo quando necessario, quando si modificano 3 visualizzazioni, quindi l'utilizzo di $ _SESSION significherebbe che è necessario caricarli e trasportarli su ogni singola richiesta di pagina.

Se non ne hai bisogno, allora puoi fare affidamento sulla sessione o anche metterla direttamente in un archivio di valori chiave atteso ($ form_state è archiviato anche lì ora, non una pseudo-cache come era in 7.x).

Il sistema di configurazione tuttavia non è una buona corrispondenza. Ciò non è destinato al contenuto (o al contenuto) per utente in quanto non si adatta realmente per archiviare migliaia o decine di migliaia di record e potrebbe fare alcune ipotesi per precaricare tutto ciò di cui potrebbe aver bisogno in una determinata richiesta di pagina ( non ancora, ma c'è un problema per farlo accadere)


Un'altra domanda sulla tua risposta. Questa potrebbe essere una domanda stupida: \ Drupal :: service ('user.tempstore') è disponibile anche per utenti anonimi?
chrisjlee,

Sì, ricade nell'ID di sessione per utenti anoemi. Vedi api.drupal.org/api/drupal/…
Berdir il

4

Normalmente è possibile memorizzare i valori dei moduli tra i passaggi utilizzando la cache degli oggetti cTools (simile ai moduli Multistep in Drupal 7 ) o tramite $form_state(come da questa esercitazione ).

In Drupal 8 puoi ereditare la FormBaseclasse per creare una nuova classe a più fasi.

Nell'articolo Come creare moduli a più passaggi in Drupal 8 puoi trovare un modo semplice per creare un modulo a più fasi in Drupal 8.

Prima di tutto, dovresti creare la classe base che sarà incaricata di iniettare le dipendenze necessarie.

Raggrupperemo tutte le classi del modulo e le inseriremo in una nuova cartella chiamata Multistepsituata nella Formdirectory dei plugin del nostro modulo demo. Questo è puramente per avere una struttura pulita ed essere in grado di dire rapidamente quali forme fanno parte del nostro processo di moduli a più fasi.

Ecco il codice demo (per 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);
    }
  }
}

Quindi è possibile creare la classe di moduli effettiva all'interno di un file chiamato 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');
  }
}

Nel buildForm()metodo stiamo definendo i nostri due elementi fittizi. Si noti che stiamo recuperando prima la definizione del modulo esistente dalla classe genitore. I valori predefiniti per questi campi sono impostati come valori trovati nell'archivio per quelle chiavi (in modo che gli utenti possano vedere i valori che hanno compilato in questo passaggio se tornano ad esso). Infine, stiamo modificando il valore del pulsante di azione su Avanti (per indicare che questo modulo non è quello finale).

Nel submitForm()metodo salviamo i valori inviati nel negozio e quindi reindirizziamo al secondo modulo (che può essere trovato sul percorso demo.multistep_two). Tieni presente che non stiamo eseguendo alcun tipo di convalida qui per mantenere il codice leggero. Ma la maggior parte dei casi d'uso richiederà una convalida dell'input.

E aggiorna il tuo file di routing nel modulo 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'

Infine, crea il secondo form ( 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');
  }
}

All'interno del submitForm()metodo salviamo nuovamente i valori nell'archivio e li rimandiamo alla classe genitore per conservare questi dati nel modo che ritengono opportuno. Quindi reindirizziamo a qualsiasi pagina desideriamo (il percorso che utilizziamo qui è fittizio).

Ora dovremmo avere un modulo multistep funzionante che utilizza il PrivateTempStoreper mantenere i dati disponibili su più richieste. Se abbiamo bisogno di più passaggi, tutto ciò che dobbiamo fare è creare altri moduli, aggiungerli tra quelli esistenti e apportare un paio di modifiche.


1

La procedura guidata a più fasi che hai menzionato, è già integrata con CTools, vedi: Supporto della procedura guidata per 8.x-3.x , quindi potresti considerare di estenderlo in your_module.services.yml, ad es.

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

quindi estendi la classe in 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 {

}

conosci un esempio che potrebbe aiutare a capire come usare la procedura guidata multistep di CTools?
Duncanmoo,

1
@Duncanmoo non ho, ma sentiti libero di fare un'altra domanda con un problema specifico che stai Tests/Wizard/CToolsWizard*riscontrando , oppure cerca nei file dove puoi trovare alcuni test (ad es testWizardSteps.).
Kenorb,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.