4

I try to implement a button to add rows to a form table. Each clic on the button should add a new line.

My problem is that my modifications in the AJAX Callback doesn't seem to modify the actual $form object :

At the first clic, row n°6 is added but without default values, wich is already a problem. The next clics have no effect, the $form parameter in the callback doesn't contain the 6th row.

What is the way to actually modify $form ?

Here is the build form :

public function buildForm(array $form, FormStateInterface $form_state, Array $profile = NULL) {

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

// table
$form['myTable'] = array(
'#type' => 'table',
'#header' => array(t('COL 1'), t('COL 2'), t('COL 3')),
'#prefix' => '<div id="my-table-wrapper">',
'#suffix' => '</div>',
);
// Build the table rows and columns.
for ($cpt = 0; $cpt <= 5; $cpt++) {
// Table row
$form['myTable'][$cpt] = $this->getTableLine($cpt);
}
$form['addRow'] = array(
    '#type' => 'button',
    '#value' => t('Add a row'),
    '#ajax' => array(
            'callback' =>  '::ajaxAddRow',
            'event' => 'click',
            'wrapper' => 'my-table-wrapper',
    ),
);

return $form;

}

Here the function called to fill the rows :

function getTableLine($key) {
    $line = array();

    $line['col_1'] = array(
            '#type' => 'textfield',
            '#default_value' => 'col 1 - row ' . $key,
    );
    $line['col_2'] = array(
            '#type' => 'textfield',
            '#default_value' => 'col 2 - row ' . $key,
    );
    $line['col_3'] = array(
            '#type' => 'textfield',
            '#default_value' => 'col 3 - row ' . $key,
    );
    return $line;
}

And the Ajax callback :

function ajaxAddRow($form, $form_state) {
    $cpt=0;
    for ($x = 0; $x <= 10 & $form['myTable'][$x]; $x++) {
        $cpt++;
    }

    // $cpt always return 6 - expected to increment
    // $form['myTable'][$cpt] is always empty

    $form['myTable'][$cpt] = $this->getTableLine($cpt);

    return $form['myTable'];

}

2 Answers 2

2

Details and Warnings

Changes to the form must only be made in the form builder function (ajax_example_autocheckboxes() in the example here), or validation will fail. The callback function must not alter the form or any other state.

Reference: https://www.drupal.org/docs/7/api/javascript-api/ajax-forms-in-drupal-7

It's for D7, but the concept how Ajax works has not changed:

The Big Idea

The big idea here is that:

  • Your form gets rebuilt when you manipulate a form element (e.g. select, submit etc.)
  • Your form builder function builds it a different way based on that input ($form_state)
  • Your #ajax settings and your callback function arrange to deliver all or a part of the newly rebuilt form to replace or otherwise enhance some part of the page.
4
  • Thank you, but in the example, the number of checkboxes to display is given by the select element, which belongs to the form.
    – user70263
    Commented Dec 6, 2016 at 14:12
  • Put all code in the form builder. In your case you don't need any code in the ajax callback. Just return the form part that you've changed in the form rebuild.
    – 4uk4
    Commented Dec 6, 2016 at 14:22
  • Sorry but in my case, I have to know how many times the 'addRow' button has been clicked. And I don't see where I can store this counter.
    – user70263
    Commented Dec 6, 2016 at 14:25
  • See the second quote from the referenced doc. But you should really read the complete documentation.
    – 4uk4
    Commented Dec 6, 2016 at 16:10
0

I managed this by detecting each click and setting a click counter in a hidden element of the form.

Everything is done in the buildForm, the callback function simply returns the form element to refresh.

public function buildForm(array $form, FormStateInterface $form_state, Array $profile = NULL) {


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

    // table
    $form['myTable'] = array(
        '#type' => 'table',
        '#header' => array(t('COL 1'), t('COL 2'), t('COL 3')),
        '#prefix' => '<div id="my-table-wrapper">',
        '#suffix' => '</div>',
    );
    // Build the table rows and columns.
    for ($cpt = 0; $cpt <= 5; $cpt++) {
        // Table row
        $form['myTable'][$cpt] = $this->getTableLine($cpt);
    }


    // Build the extra lines
    $triggeringElement = $form_state->getTriggeringElement();
    $clickCounter = 0;
    // if a click occurs 
    if ($triggeringElement and $triggeringElement['#attributes']['id'] == 'add-row') {
        // click counter is incremented
        // $formstate and $form element are updated
        $clickCounter=$form_state->getValue('click_counter');
        $clickCounter++;
        $form_state->setValue('click_counter',$clickCounter);
        $form['click_counter'] = array('#type' => 'hidden', '#default_value' => 0, '#value' => $clickCounter);
    } else {
        $form['click_counter'] = array('#type' => 'hidden', '#default_value' => 0);
    }

    // Build the extra table rows and columns.
    for ($k=0 ; $k<$clickCounter ; $k++) {
        $form['myTable'][6+$k] = $this->getTableLine(6+$k);
    }


    $form['addRow'] = array(
            '#type' => 'button',
            '#value' => t('Add a row'),
            '#ajax' => array(
                    'callback' =>  '::ajaxAddRow',
                    'event' => 'click',
                    'wrapper' => 'my-table-wrapper',
            ),
            '#attributes' => array(
                    'id' =>  'add-row'
            ),
    );

    return $form;

}

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.