0

I'm trying to write an AJAX form in Drupal 8. I managed to get a simple example working yesterday, but today, my work fails without error. I was wondering whether anyone here could spot my error.

Here are the relevant excerpts from my OverviewForm:

public function buildForm(array $form, FormStateInterface $form_state) {

  ...

  $form['job_id'] = [
    '#weight' => 1,
    '#title' => 'Job',
    '#type' => 'select',
    '#options' => $formatted_jobs,
    '#ajax' => [
      'callback' => '::showFiles',
      'event' => 'change',
      'wrappper' => 'jobfiles'
    ]
  ];

  $form['jobfiles'] = [
    '#weight' => 2,
    '#title' => 'Files',
    '#type' => 'html_tag',
    '#tag' => 'span',
    '#value' => 'Please make a selection above',
    '#attributes' => [
      'id' => ['jobfiles']
    ] 
  ];

  return $form;
}

public function showFiles(array &$form, FormStateInterface $form_state) {
  $job_id = $form_state->getValue('job_id');
  $elem = [
    '#weight' => 2,
    '#type' => 'item_list',
    '#items' => $this->_files($job_id),
    '#attributes' => [
      'id' => ['jobfiles']
    ]
  ];

  return $elem;
}

The list of jobs populates correctly, but when I select one, the initial jobfiles span isn't replaced with anything. I'd expect it to at least disappear when I select a value from the select. Thanks in advance!

1

I should try using #prefix/#suffix for wrapper instead the element itself. Also IMHO it's not a good idea mess around with the elements you send during build. Take in mind the the build is done every time you render the form, even for AJAX calls. A possible solution could be hide the second element using Form States API and put the Please make selection in your prior element.

public function buildForm(array $form, FormStateInterface $form_state) {
  // Code excerpt.
  $form['job_id'] = [
    '#weight' => 1,
    '#title' => t('Job'),
    '#type' => 'select',
    '#empty_value' => t('Please select a job'),
    '#options' => $formatted_jobs,
    '#ajax' => [
      'callback' => [get_class($this), 'showFiles'],
      'wrapper' => 'ajax-wrapper',
      'event' => 'change',
    ],
    '#required' => TRUE,
  ];

  // Render a hidden and empty list.
  $form['jobfiles'] = [
    '#weight' => 2,
    '#title' => t('Files'),
    '#type' => 'item_list',
    '#items' => [],
    '#prefix' => '<div id="ajax-wrapper">',
    '#suffix' => '</div>',
    '#attributes' => [
      'class' => ['invisible']
    ]
  ];

  return $form;
}

public function showFiles(array &$form, FormStateInterface $form_state) {
  $job_id = $form_state->getValue('job_id');
  $form['jobfiles']['#items'] = $this->_files((int)$job_id);
  // Remove the 'invisible' class.
  $form['jobfiles']['#attributes']['class'] = array_filter($form['jobfiles']['#attributes']['class'], function($v) {
    return $v != 'invisible';
  });
  // You could also return the element instead.
  $response = new AjaxResponse();
  $response->addCommand(new ReplaceCommand('#ajax-wrapper', [$form['jobfiles']]));

  return $response;
}

Hope this may help you.

Update: Replace $element with $form. Also worth checking the answer provided by @joshua-moore as it is valid too.

  • Thanks for taking a look at my question. Unfortunately, all I get is the little spinner and a little Please Wait sign. It disappears after half a second or so, but nothing shows up, even when I hardcode $this->_files($job_id). – Joshua Moore Aug 1 '18 at 16:16
  • With a little bit of extra googling, I got your answer to work. Thank you! Have my upvote... – Joshua Moore Aug 1 '18 at 16:31
1

I'm posting this answer in the hopes that it saves someone else some googling.

  public function buildForm(array $form, FormStateInterface $form_state) {
    ...

    $form['job_id'] = [
      '#weight' => 1,
      '#title' => t('Job'),
      '#type' => 'select',
      '#options' => $formatted_jobs,
      '#ajax' => [
        'callback' => '::showFiles',
        'wrapper' => 'ajax-wrapper'
      ],
      '#required' => TRUE
    ];

    $form['jobfiles'] = [
      '#weight' => 2,
      '#title' => t('Files'),
      '#theme' => 'item_list',
      '#items' => [],
      '#prefix' => '<div id="ajax-wrapper">',
      '#suffix' => '</div>'
    ];

    return $form;
  }

  public function showFiles(array &$form, FormStateInterface $form_state) {
    $job_id = $form_state->getValue('job_id');

    $elem = [
      '#title' => t('Files'),
      '#weight' => 2,
      '#theme' => 'item_list',
      '#items' => $this->_files((int)$job_id)
    ];

    $renderer = \Drupal::service('renderer');

    $response = new AjaxResponse();
    $response->addCommand(new HtmlCommand('#ajax-wrapper', $renderer->render($elem)));

    return $response;
  }

This works. Thank you, d70rr3s for your answer.

  • Hmm the render array wasn't getting rendered? Interesting :o I'll take a closer look but I though both AjaxResponse or the returning value from the AJAX callback can be either a render array or rendered output. Thanks for the upvote, I'll update my answer with your code. – d70rr3s Aug 1 '18 at 22:02
  • I think I see why my code was not working and is because in the showFiles method I'm using $element instead of $form :) any way thanks! – d70rr3s Aug 1 '18 at 22:09

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.