1

I have a custom module that uses a form in a block that will be displayed on a node page. This form is used to retrieve datas from a custom table. What I want to achieve is :

  1. The user arrives on the page : the form is displayed and the 10 lasts items retrieved from the database are also displayed
  2. The user selects a data via the form
  3. The user submits the form
  4. The node page containing the block is refresehed with the 10 new items retrieved from database. I also need the form to be displayed on top of the block with previously selected values selected

I've been able to create a form class extending the FormBase class. Then I've created a block extending BlockBase class. In the build method I get the form using the Drupal::formBuilder()->getForm() method

public function build(){

$form = \Drupal::formBuilder()->getForm('Drupal\my_module\Form\my_form');

  return array(
      '#theme' => 'my_theme',
      '#form' => $form,
    );
}

At that step, my node page + the block containing the form is correctly displayed, but I've not been able to render the default 10 last items. I've no idea on how to achieve this since the buildForm method of the form class is supposed to return an array to render the form. I've tried to implement it in the build method of my block :

return array(
      '#theme' => 'my_theme',
      '#items' => $items,
      '#form' => $form,
    );

It works, but after form submission, those first 10 default items are always displayed because the block build them on every refresh. Is there a way to know if the included form has been submitted ?

Concerning the form validation, redirection is definitly not the way I should go since a need the results of the database query to be displayed in the same page / block.

Based on this topic, I feel the $form_state->setRebuild() / $form_state->isRebuilding() methods are the way to go. Do you confirm ?

I've been able to refresh the item list after form submission doing so :

  if ($form_state->isRebuilding()) {
  $myfield_value = $form_state->getValue('myfield ');

  $model = Drupal::service('my_module.model');
  $items = $model->getLastItems(10, $myfield_value );

  return array(
    '#theme' => 'my_theme',
    '#form' => $form,
    '#items' => $items,
  );

}

This works, but only half of the time the form is submitted. The second time it's like it never passes in the if($form_state->isRebuilding()) {} statement. Moreover, the results are nested in the form markup. Maybe it's not the proper way to inject the form...

For information here is an example of my twig markup :

{{ form }}
<div class="row">
    {% for track in tracks %}
        {# print stuff...#}
    {% endfor %}
</div>

To be able to inject the form in the if($form_state->isRebuilding()) statement of the buildForm of the form class, I must wait the form array is completly defined in code... Doing so, I'm unable to set fields values to the ones the user has submitted.

In brief, my questions are :

  • How can I populate the default items list before the form is submitted ?
  • How can I properly inject the form from the if($form_state->isRebuilding() statement, so that everything works
  • How can I set the fields selected values after form submission.

Sorry for this long post, but I'm quite new to the drupal ecosystem, so any help would be greatly appreciated !...

Cheers

PS : I've also read about the BlockBase::blockForm and BlockBase::blockSubmit methods, but as far as I understand, it's more about block configuration froms. Does somebody confirm that ?

  • It' difficult to answer, because the fact that the form is in a block is not connected to how to rebuild a form. Better test the form class in a form route and when this works put it in a block. This example might help to understand how forms rebuild: drupal.stackexchange.com/questions/223780/… – 4k4 Mar 9 '17 at 16:22
2

Thanks 4k4, the main point I was missing here is that a form is just a specific form of render array... I though it was only possible to define form elements to be rendered. But as you pointed me out, it's possible to define anything one wants to render, like... form result... Since what I need to render is a bit more complicated than simple markup, I've created a new template in my .module file :

function my_module_theme() {
return array(
'my_module_form' => array(
  'variables' => array(
    'form' => array(),
  ),
'template' => 'my-module-form',
),
 'my_module_results' => array( 
  'variables' => array(
    'tracks' => array(),
  ),
 }

And then, my buildForm method looks approximately that way :

public function buildForm(array $form, FormStateInterface $form_state) {
// define default values...
$defaultValue = 3;

// define form fied + submit button...
 $form['my_field'] = array(
  '#type' => 'select',
  '#required' => true,
  '#default_value' => $defaultValue,
  '#options' => array(1 => 1, 2 => 2, 3 => 3)
);

$form['submit'] = array(
  '#type' => 'submit',
  '#value' => 'Go'
);

// define field default value that will be overwritten whe  form is submitted 
$my_field_value = 1;


//get submitted value
if ($form_state->isRebuilding()){
    $my_field_value =  $form_state->getValue('my_field');
}

//get datas from database
$results = $model->getResultFromDb($my_field);

 $form['result'] = array(
    '#theme' => 'my_module_results',
    '#results' => $results,
    );

return $form;
}

Then is the "my_module_results" you can use the $results var as in any other template :

<ul>
  {% for result in results %}
  <li> {{ result.title }}</li>
  {% endfor %}
</ul>

For the results not to be nested in the "form" markup, I had to build manually the form in its own template and call the result var where I needed the result template to be inserted :

<div id="form">
    <form method="{{ form['#method'] }}">
        {{ form.form_build_id }}
        {{ form.form_token }}
        {{ form.form_id }}
        {{ form.my_field }}
        {{ form.submit }}
    </form>             
</div>
<div id="results">
    {{ form.result }}
</div>

Not sure it wes the only way to do that, but it works like a charm...

Hope this would help somebody...

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.