2

I'm trying to add AJAX callback to form element that was previously rendered by AJAX but with no luck. Code below.

A bit of description. There are three main methods here:

  1. buildForm - base form
  2. extraField - AJAX callback attached to ['month']
  3. choosePerson - AJAX callback attached to: ['week_day']['hour']

In the buildForm method I create base form with AJAX callback on 'month' select element. In the AJAX callback you can find an extraField method. This method contains form element called 'hour' ($form['week_day']['hour']) and this element renders well. But this element also contains AJAX callback, which doesn't work, it does not generate 'person_container' element and the 'person' select.

Any idea how to ajaxify elements that previously were rendered by ajax?

class ReservationForm extends FormBase {

  public function getFormId() {
    return 'reservation_form';
  }

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

    $form['month'] = array(
      '#type' => 'select',
      '#title' => $this->t('Select month'),
      '#options' => array($this->t('January'), $this->t('February'), $this->t('March')),
      '#ajax' => array(
        'callback' => array($this, 'extraField'),
        'event' => 'change',
        'wrapper' => 'week-day',
      ),
    );

    // Disable caching on this form.
    $form_state->setCached(FALSE);

    $form['week_day'] = [
      '#type' => 'container',
      '#attributes' => ['id' => 'week-day'],
    ];
  }

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

    $hour_options = array();

    if ($month == '0') {
      $hour_options[0] = $this->t('08:00');
      $hour_options[1] = $this->t('09:00');
      $hour_options[2] = $this->t('10:00');
    }

    if ($month == '1') {
      $hour_options[0] = $this->t('12:00');
      $hour_options[1] = $this->t('13:00');
      $hour_options[2] = $this->t('14:00');
    }

    if ($month == '2') {
      $hour_options[0] = $this->t('18:00');
      $hour_options[1] = $this->t('19:00');
      $hour_options[2] = $this->t('20:00');
    }

    $form['week_day']['hour'] = array(
      '#type' => 'select',
      '#title' => $this->t('Choose hours @month', array('@month' => $month)),
      '#options' => $hour_options,
      '#ajax' => array(
        // tried with that callback too: [ $this, 'choosePerson']
        'callback' => array($this, 'choosePerson'),
        'event' => 'change',
        'wrapper' => 'person',
      ),
    );

    $form['week_day']['person_container'] = array(
      '#type' => 'container',
      '#attributes' => ['id' => 'person-container'],
    );

    $form['week_day']['person_container']['person'] = array(
      '#type' => 'select',
      '#title' => $this->t('Choose person'),
      '#options' => ['John', 'Sarah', 'Peter'],
    );

    return $form['week_day'];
  }

  public function choosePerson(array &$form, FormStateInterface $form_state) {
    $form['week_day']['person_container']['person'] = array(
      '#type' => 'select',
      '#title' => $this->t('Choose person'),
      '#options' => ['John', 'Sarah', 'Peter'],
    );

   return $form['week_day']['person_container'];
  }
}  
  • I have the same problem, is there a solution for that? – schneidolf Sep 7 '17 at 8:34
  • 1
    @Wolf, there are hundreds of these questions on DA, you just picked one. Not all are answered, because there are so many. You can't build form elements in an ajax callback. See Ajax Forms in Drupal 7, which is still valid for D8. – 4k4 Sep 7 '17 at 13:39
  • Thank you, for the link, now I got it. I will post the right solution in an answer. – schneidolf Sep 8 '17 at 6:26
5

The Link 4k4 posted in the discussion of the question gives the solution:

Create a callback function (named by the #ajax['callback']). This is generally a very simple function which does nothing but select and return the portion of the form that is to be replaced on the original page. Note, that as part of the Form APIs security system, you cannot create new form elements in the callback function, as they will throw errors upon submission, and any #ajax on elements created in the callback will also not work. If you need to create new elements on ajax submit, they must be added in the form definition. You can use the values in $form_state to determine whether the form build process is the initial form load, or an #ajax initiated load.

So the following code example is working.

class ReservationForm extends FormBase {

  public function getFormId() {
    return 'reservation_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['month'] = [
      '#type' => 'select',
      '#title' => $this->t('Select month'),
      '#options' => [
        $this->t('January'),
        $this->t('February'),
        $this->t('March'),
      ],
      '#ajax' => [
        'callback' => [$this, 'extraField'],
        'event' => 'change',
        'wrapper' => 'week-day',
      ],
    ];

    // Disable caching on this form.
    $form_state->setCached(FALSE);

    $form['week_day'] = [
      '#type' => 'container',
      '#attributes' => ['id' => 'week-day'],
    ];

    if ($form_state->getUserInput()['_triggering_element_name'] == 'month') {
      $month = $form_state->getValue('month');
      $hour_options = [];

      if ($month == '0') {
        $hour_options[0] = $this->t('08:00');
        $hour_options[1] = $this->t('09:00');
        $hour_options[2] = $this->t('10:00');
      }

      if ($month == '1') {
        $hour_options[0] = $this->t('12:00');
        $hour_options[1] = $this->t('13:00');
        $hour_options[2] = $this->t('14:00');
      }

      if ($month == '2') {
        $hour_options[0] = $this->t('18:00');
        $hour_options[1] = $this->t('19:00');
        $hour_options[2] = $this->t('20:00');
      }

      $form['week_day']['hour'] = [
        '#type' => 'select',
        '#title' => $this->t('Choose hours @month', ['@month' => $month]),
        '#options' => $hour_options,
        '#ajax' => [
          'callback' => [$this, 'choosePerson'],
          'event' => 'change',
          'wrapper' => 'person-container',
        ],
      ];

      $form['week_day']['person_container'] = [
        '#type' => 'container',
        '#attributes' => ['id' => 'person-container'],
      ];

      $form['week_day']['person_container']['person'] = [
        '#type' => 'select',
        '#title' => $this->t('Choose person'),
        '#options' => ['John', 'Sarah', 'Peter'],
      ];
    }

    if ($form_state->getUserInput()['_triggering_element_name'] == 'hour') {
      $form['week_day']['person_container']['person'] = [
        '#type' => 'select',
        '#title' => $this->t('Choose person'),
        '#options' => ['Hans', 'Frank', 'Emma'],
      ];
    }

    return $form;
  }

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

  public function extraField(array &$form, FormStateInterface $form_state) {
    return $form['week_day'];
  }

  public function choosePerson(array &$form, FormStateInterface $form_state) {
    return $form['week_day']['person_container'];
  }

}
  • Ok - my answer lacked the callback and show hide part. Was a quicky ;) – rémy Sep 8 '17 at 11:45

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.