11

I have a controller that returns a normal renderable page array. I'd like to embed a form in that page, but the form is not appearing in the rendered output. The form is properly added to the renderable array correctly, but it's not in the final HTML for some reason. What's going on here?

Here's my controller (which extends ControllerBase through BlockController):

use Drupal\block\Controller\BlockController;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Render\Element;
use Symfony\Component\DependencyInjection\ContainerInterface;

class LayoutController extends BlockController {

  /**
   * @var \Drupal\Core\Form\FormBuilderInterface
   */
  protected $formBuilder;

  /**
   * {@inheritdoc}
   */
  public function __construct(ThemeHandlerInterface $theme_handler, FormBuilderInterface $form_builder) {
    parent::__construct($theme_handler);
    $this->formBuilder = $form_builder;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('theme_handler'),
      $container->get('form_builder')
    );
  }

  /**
   * Overrides the region demo page, adding placeholder elements for sortable
   * lists of blocks, plus a control panel form.
   *
   * @param string $theme
   *   The theme key.
   *
   * @return array
   *   The renderable page.
   */
  public function layout($theme) {
    $page = parent::demo($theme);

    foreach (Element::children($page) as $region) {
      if (isset($page[$region]['block_description'])) {
        $page[$region]['block_description']['#template'] .= '<ul data-region="{{ region }}"></ul>';
        $page[$region]['block_description']['#context']['region'] = $region;
      }
    }
    $page['layout_control'] = $this->formBuilder->getForm('Drupal\block_ui\Form\LayoutControlForm');
    $page['#attached']['library'][] = 'block_ui/backbone.collectionView';

    return $page;
  }

}

3 Answers 3

10

To embed a form in a renderable array, you use:

$form_class = '\Drupal\my_module\Form\MyForm';
$build['form'] = \Drupal::formBuilder()->getForm($form_class);

Which is similar to your code.

But the problem is of a different nature. You are extending a special controller, one that returns a renderable array of '#type' => 'page'. It uses the page template to render the output and that template does not contain the {{ layout_control }} variable to output your form. If you use $page['header']['layout_control'] = ... to add the form to the render array, it should show the form in the header region of the layout demo page.

3
  • This works for empty forms, but how would you get the $form_state after submission? Commented Sep 10, 2018 at 9:29
  • Form validation and submit is handled within the MyForm class using the ::validateForm and ::submitForm methods. That is where you get the form state.
    – Sutharsan
    Commented Oct 16, 2018 at 14:23
  • 1
    What I meant is: How can you acess the $form_state from the Controller after submission? Commented Oct 16, 2018 at 14:33
11

You can render a form and place it wherever you want in your markup.

In your controller method, you want to do $this->formBuilder()->getForm() and then render that form array using \Drupal::service('renderer')->render()

I'm not sure what side effects this might have with submission / error control if you have multiple forms on a single page but it is possible to do it.

See below controller

<?php

namespace Drupal\my_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\Markup;

class MyController extends ControllerBase
{

    public function displayForm()
    {

        $myForm = $this->formBuilder()->getForm('Drupal\my_module\Form\MyForm');
        $renderer = \Drupal::service('renderer');
        $myFormHtml = $renderer->render($myForm);

        return [
            '#markup' => Markup::create("
                <h2>My Form is Below</h2>
                {$myFormHtml}
                <h2>My Form is Above</h2>
            ")
        ];
    }
}
2

So it turns out that Drupal 8 doesn't let you to do the kind of loose, willy-nilly page alteration that Drupal 7 allowed. Instead, you have to use one of the hooks which replace hook_page_build() and hook_page_alter(). In particular, hook_page_top() and hook_page_bottom() are where you can insert arbitrary renderable elements.

There's an API change record explaining this: https://www.drupal.org/node/2357755.

Here's the code I used to fix my problem (in the .module file):

/**
 * Implements hook_page_bottom().
 */
function block_ui_page_bottom(array &$page_bottom) {
  $route_match = \Drupal::routeMatch();

  if ($route_match->getRouteName() == 'block.admin_display_theme') {
    $region_list = system_region_list($route_match->getParameter('theme'), REGIONS_VISIBLE);
    $form_state = new FormState();
    $form_state->set('region_list', $region_list);
    $page_bottom['layout_control'] = \Drupal::formBuilder()->buildForm('\Drupal\block_ui\Form\LayoutControlForm', $form_state);
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.