1

Update

I got an example working at least to the point where a form is viewed. But there are still lots of issues, like the AJAX in the form not working and also the Next/Prev buttons do nothing. If someone has a really working example I would be happy for a reference.

My current version can be found at https://github.com/marcusx/ct_wizard_content_entity_example


Original Problem Description

I want to build a multistep form with content entity forms using the ctools wizard API. In addition to the normal process of adding a new entity with the default add/edit form (with is very complex and suitable for admins only), an additional route shall display variants of the edit form in multiple steps.

So my-entity/add/my-bundle shows the normal add/edit form. And my-entity/wizard/my-bundle should display the first step of the wizard with a simplified version of the my-entity edit form.

As far I can see from the ctools_wizard_test module See:(ctools_wizard_test code) this involves the following tasks:

  • Implement a wizard form class by extending EntityFormWizardBase.
  • Add wizard annotation to the entity class.
  • Add routing for the wizard form.
  • Add forms for each step in the wizard and reference them in the $steps array returned by getOperations in my wizard class.

My implementation is working till the point where the WizardFactory is building the content entity form. Here I get lots of errors because the $entity on the entity form is not set. On regular content entity forms this gets set in \Drupal\Core\Entity\HtmlEntityFormController::getFormObject. As wizard forms bring their own controller this doesn't happen.

Where is the appropriate place to set the entity in the form class?

  • We have the entity in the wizard class but there seems no way to pass it on to the form properly.
  • It gets added to the $form_state in \Drupal\ctools\Wizard\WizardFactory::getFormState where you could theoretically get it in the form class from $form_state->getTemporaryValue.
  • Whatever method of the entity form I overwrite to call $this->setEntity() it seems always too late. I get sooner or later a Fatal error: Call to a member function whatever on null as the entity is not set early enough.

Entity Annotation

 *     "wizard" = {
 *       "add" = "Drupal\my_custom_entity\Wizard\MyEntityAddWizard",
 *     },

My Wizard Class:

namespace Drupal\my_custom_entity\Wizard;

use Drupal\ctools\Wizard\EntityFormWizardBase;
use Drupal\ctools\Event\WizardEvent;
use Drupal\ctools\Wizard\FormWizardInterface;

class MyEntityAddWizard extends EntityFormWizardBase {

  /**
   * The entity.
   *
   * @var \Drupal\my_custom_entity\Entity\MyEntityInterface
   */
  public $entity;

  /**
   * {@inheritdoc}
   */
  public function getWizardLabel() {
    return $this->t('My Entity');
  }

  /**
   * {@inheritdoc}
   */
  public function getMachineLabel() {
    return $this->t('Label');
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityType() {
    return 'my_entity';
  }

  /**
   * {@inheritdoc}
   */
  public function exists() {
    return '\Drupal\my_custom_entity\Entity\MyEntity::load';
  }

  /**
   * {@inheritdoc}
   */
  public function initValues() {
    $storage = $this->entityManager->getStorage($this->getEntityType());

    $content_entity_type = $this->routeMatch->getParameter('my_entity_type');
    $entity = $storage->create(['type' => $content_entity_type->id()]);
    $values[$this->getEntityType()] = $entity;
    $this->entity = $entity;

    $event = new WizardEvent($this, $values);
    $this->dispatcher->dispatch(FormWizardInterface::LOAD_VALUES, $event);
    return $event->getValues();
  }

  /**
   * {@inheritdoc}
   */
  public function getOperations($cached_values) {

    $steps = [
      'create' => [
        'form' => 'Drupal\my_custom_entity\Form\MyEntityWizardForm',
        'title' => $this->t('Create'),
      ],
      'content' => [
        'form' => 'Drupal\my_custom_entity\Form\MyEntityForm',
        'title' => $this->t('Content'),
      ],
    ];

    return $steps;
  }
}

My Form for the first wizard step:

namespace Drupal\my_custom_entity\Form;

use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;

/**
 * Form controller for MyEntity edit forms.
 *
 * @ingroup my_custom_entity
 */
class MyEntityWizardForm extends MyEntityForm {

  /**
   * Initialize the form state and the entity before the first form build.
   */
  protected function init(FormStateInterface $form_state) {
    /* @var $entity \Drupal\my_custom_entity\Entity\MyEntity */
    $entity = $form_state->getTemporaryValue('wizard')['my_entity'];
    $this->setEntity($entity);
    $this->setModuleHandler(\Drupal::service('module_handler'));
    parent::init($form_state);

  }
}

my_custom_entity.routing.yml

entity.my_entity.wizard:
  path: '/my-entity/wizard/{my_entity_type}'
  defaults:
    _entity_wizard: 'my_entity.add'
    _title: 'Add My Entity'
    tempstore_id: 'my_custom_entity.my_entity'
  requirements:
    _permission: 'administer site configuration'
  options:
    parameters:
      my_entity_type:
        type: entity:my_entity_type
  • I am looking for the same solution. Were you able to resolve your issue? – Sukhjinder Singh Jan 24 '18 at 13:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.