0

In Drupal 8.4.3, I tried to render a form inside a controller's method, but it does not render as usual.

  • The value of #default_value is not taken into account
  • Fields have no name attribute (which causes them not to be sent)

This is the smallest code I've been able to write to reproduce the issue.

public function list() 
{
  $render = array(
    '#type' => 'form',
    'search' => array(
      '#type' => 'textfield',
      '#title' => 'Search',
      '#default_value' => 'Lorem ipsum'
    ),
    'actions' => array(
      '#type' => 'actions',
      'submit' => array(
        '#type' => 'submit',
        '#value' => 'Apply'
      )
    )
  );

  return $render;
}

The rendered markup is the following.

<form method="post" accept-charset="UTF-8">
  <div class="js-form-item form-item js-form-type-textfield form-type-textfield js-form-item- form-item-">
    <label>Search</label>
    <!-- No name attribute, nor a value -->
    <input type="text" size="60" maxlength="128" class="form-text">
  </div>
  <input type="submit" name="op" value="Apply" class="button js-form-submit form-submit">
</form>

The sent data is the following. Obviously, the search field is not sent as it does not have a name.

op=Apply

I can overcome the missing name attribute with a #name key, but I cannot understand why it is needed here, but no where else.

This is the most basic route/controller pattern.

What could interfere in my context?

  • 2
    You're bypassing the form builder, so it makes sense that functionality would be missing. You need to create a form the standard way, i.e. with a class that extends FormBase, and then use the form builder to prepare it for render. That will definitely work – Clive Dec 15 '17 at 16:18
  • It makes sense... So there is no way to create a form on-the-fly? – zessx Dec 15 '17 at 16:21
  • Yep, use the form builder :) If you mean from just an array then no, I doubt it. Drupal 8 is a lot more structured – Clive Dec 15 '17 at 16:22
  • Using \Drupal::formBuilder() worked as a charm, thanks. Feel free to add an answer for me to accept it. If you don't care, I'll take the time to write one for future users. – zessx Dec 15 '17 at 16:32
1

It's not possible to use form elements in a controller directly, only render elements. You find a complete list of both types here: https://api.drupal.org/api/drupal/elements/8.4.x

To use form elements in a page controller you need a form route:

example.routing.yml:

example.form:
  path: '/example-form'
  defaults:
    _title: 'Example form'
    _form: '\Drupal\example\Form\ExampleForm'
  requirements:
    _permission: 'access content'

And then build the class ExampleForm by extending FormBase. See https://www.drupal.org/docs/8/api/form-api/introduction-to-form-api

This is the most basic route-controller pattern for forms.

  • In fact, form is a render element. – zessx Dec 15 '17 at 16:30
  • Yes, but you can use render elements in forms. Only the other way is not possible, because form elements are an extended version of render elements with extra methods only forms can process. – 4k4 Dec 15 '17 at 16:33
  • Nah, you still can use them as render element. Using \Drupal::formBuilder() to build it, then adding it to an existing render array (we agree that you'll need a Form class anyway, and that was my mistake) – zessx Dec 15 '17 at 16:40
  • The main point of my answer was a route-controller solution (without the need to use the formBuilder). A lot of form elements can't be rendered outside of forms, because the render output is only generated when the form is processed. table is the only form element I know which can work outside of a form. – 4k4 Dec 15 '17 at 16:45
  • Oh ok, sorry for misleading you. My example surely should be accomplished with a Form only (without Controller), but it was a minimal one, where I removed everything else from my Controller to focus on the main issue. The "real example" render a table, with filters above (this is why I needed to load a form). – zessx Dec 15 '17 at 16:50
0

As stated by @Clive, I was bypassing the form builder. Even if the form was visually rendered, it was far from something fully working.

This answer shows how I managed to stick to my original need, which was to load a form and render it inside a controller.

You'll first need a class inheriting FormBase, as any form you would usually create. No need for a route.

class CustomForm extends FormBase
{
  public function getFormId()
  {
    return 'custom_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state, $id = null)
  {
    $form['search'] = array(
      '#type' => 'textfield',
      '#title' => 'Search',
      '#default_value' => 'Lorem ipsum'
    );
    $form['actions'] = array(
      '#type' => 'actions',
      'submit' => array(
        '#type' => 'submit',
        '#value' => 'Apply'
      )
    );    
    return $form;
  }

  public function submitForm(array &$form, FormStateInterface $form_state)
  {
    return;
  }
}

You then will need the formBuilder service to properly load it inside your controller:

class CustomController extends ControllerBase
{
  public function foobar()
  {
    $render = array();

    $form = \Drupal::formBuilder()->getForm(CustomForm::class);
    $render['form'] = $form;

    // $render['whatever'] = ...

    return $render;
  }
}

Your Answer

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

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