3

enter image description here

So this one has me kind of stumped.

With a simple table with 4 rows, I drag one row to another position, then hit save (submit). When the ajax returns and replaces the form, the rows snap back to the original position. If I reload the page, I can see the order in which I dragged, did indeed get saved.

I reload the page, starting over, but this time start by pressing save, before dragging anything - and nothing appears to happen. But all form inputs id's and names now have a random-string, e.g. --XjidQzhH, appended to them. I can now drag a row and hit save again - and it actually works! It stays where I dragged it, also when reloading the page.

Once the form has been replaced the first time, everything seems to work as it should.

Hopefully someone knows what I'm doing wrong here - I've been rabbit-holing this for two days now and can't figure out what I'm missing.

Happens for me on Drupal 8.9.13, both with Bartik and Bootstrap themes.

Here's some reproducable code:

namespace Drupal\some_module_name\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;


class TestForm extends FormBase {


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


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

        #If form is shown for the first time, get some default values or previous values from session
        $values = $form_state->getValues();
        if (!$values){

            #Load data from session if exists, otherwise we use some temp test data. 
            #Using persistant data to allow for manual reload of page, in order to demonstrate that data and sorting of rows is saved correctly.
            $names = @$_SESSION['test_data_names'];
            if (!$names){
                $values =   [
                    "names"=>[
                        ["first_name"=>"ONE",   "last_name"=>"ONE",],
                        ["first_name"=>"TWO",   "last_name"=>"TWO",],
                        ["first_name"=>"THREE", "last_name"=>"THREE",],
                        ["first_name"=>"FOUR",  "last_name"=>"FOUR",],
                    ],
                ];
            }else{
                $values = ["names"=>$names,];
            }

            $form_state->setValues($values);
        }

        #Build the simple table for the form
        $form['names'] = [
            "#type"=>"table",
            "#tree"=>true,
            '#empty' => t('No names yet.'),
            '#tabledrag' => [[
                'action' => 'order',
                'relationship' => 'sibling',
                'group' => "group-order-weight",
            ]],
            '#header' =>[
                ["data"=>t("First name"),],
                ["data"=>t("Last name(s)"),],
                ["data"=>t("Weight"),],
            ],
        ];
        
        #Build table rows for the name inputs
        foreach ($values['names'] as $key=>$name_data){
            
            $row = [
                '#attributes'=>[
                    'class' => ['draggable'],
                ],

                "first_name"=>[
                    "#type"             => "textfield",
                    "#default_value"    => $name_data['first_name'],
                ],
                
                "last_name"=>[
                    "#type"             => "textfield",
                    "#default_value"    => $name_data['last_name'],
                ],

                "weight"=>[
                    "#type"             => "weight",
                    "#attributes"=>[
                        'class' => ["group-order-weight"],
                    ],
                ],                
            ];
            $form['names'][] = $row;
            
        }

        $form['save'] = [
            "#type"             => "submit",
            "#value"            => t("Save"),
            '#submit'           => ['::ajax_save_submit'],
            '#ajax'             => [
                'callback'  => '::ajax_save_submit_callback',
                'wrapper'   => 'table-wrapper',
            ],
        ];

        return $form;

    }
    

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

        parent::validateForm($form, $form_state);
        // return $form;

    }
    

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

    }


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

        $response = new AjaxResponse();
        $response->addCommand(new ReplaceCommand('#testform', $form));
        return $response;

    }


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

        #Get the names and save 'em in session so they persist if user reloads page
        $names = $form_state->getValue("names");

        $_SESSION['test_data_names'] = $names;
    
        $form_state->setValue("names",$names);
        $form_state->setRebuild(TRUE);

    }

}

3

Looks like the culprit was how I was adding the name rows to the form.

Wrong:   $form['names'][] = $row;
Correct: $form['names'][$key] = $row;

The reason it was "working" after the first submit, was that the replaced form had appended token id's to the form, so the replacement could not occur on the #testform id, as it no longer existed.

Here's the updated code that works, which also includes functionality to insert new rows:

namespace Drupal\some_module_name\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;


class TestForm extends FormBase {


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


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

        $values = $form_state->getValues();
        if (!$values){
            $values =   [
                "names"=>[
                    ["first_name"=>"ONE",   "last_name"=>"ONE",   ],
                    ["first_name"=>"TWO",   "last_name"=>"TWO",   ],
                    ["first_name"=>"THREE", "last_name"=>"THREE", ],
                    ["first_name"=>"FOUR",  "last_name"=>"FOUR",  ],
                ],
            ];

            $form_state->setValues($values);
        }
    
        #Build the simple table for the form
        $form['names'] = [
            "#type"=>"table",
            "#tree"=>true,
            '#empty' => t('No names yet.'),
            '#tabledrag' => [[
                'action' => 'order',
                'relationship' => 'sibling',
                'group' => "group-order-weight",
            ]],
            '#header' =>[
                ["data"=>t("First name"),],
                ["data"=>t("Last name(s)"),],
                ["data"=>t("Weight"),],
            ],
            '#prefix'      => "<div id='table-wrapper' class='col-sm-12'>",
            '#suffix'      => '</div>',

        ];

        #sort by weights - not necessary since we receive them in correct order in the array
        // uasort($values['names'], 'Drupal\Component\Utility\SortArray::sortByWeightElement');

        #Build table rows for the name inputs
        foreach ($values['names'] as $key=>$name_data){
            
            $row = [
                '#attributes'=>[
                    'class' => ['draggable'],
                ],

                "first_name"=>[
                    "#type"             => "textfield",
                    "#default_value"    => $name_data['first_name'],
                ],
                
                "last_name"=>[
                    "#type"             => "textfield",
                    "#default_value"    => $name_data['last_name'],
                ],

                "weight"=>[
                    "#type"             => "weight",
                    "#attributes"=>[
                        'class' => ["group-order-weight"],
                    ],
                ],                    
            ];
            $form['names'][$key] = $row;
            
        }

        $form['save'] = [
            "#type"             => "submit",
            "#value"            => t("Save"),
            '#submit'           => ['::ajax_save_submit'],
            '#ajax'             => [
                'callback'  => '::ajax_save_submit_callback',
                'wrapper'   => 'table-wrapper',
            ],
        ];

        $form['add'] = [
            "#type"             => "submit",
            "#value"            => "Insert row",
            '#submit'           => ['::ajax_insert_row_submit'],
            '#ajax'             => [
                'callback'  => '::ajax_insert_row_submit_callback',
                'wrapper'   => 'table-wrapper',
            ],
        ];
        
        return $form;

    }
    

    // public function validateForm(array &$form, FormStateInterface $form_state) {
    //     $values = $form_state->getValues();
    //  parent::validateForm($form, $form_state);
    // }
    

    public function submitForm(array &$form, FormStateInterface $form_state) {
        #has to be defined, otherwise we get errors like:
        #TestForm contains 1 abstract method and must therefore be declared abstract or implement the remaining methods
    }



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


    public function ajax_save_submit(array &$form, FormStateInterface $form_state){
        $names = $form_state->getValue("names");
        
        #save logic goes here..

        $form_state->setValue("names",$names);
        $form_state->setRebuild(true);
    }
    

    public function ajax_insert_row_submit(array &$form, FormStateInterface $form_state){
        $names = $form_state->getValue("names");
        $names[] = ["first_name"=>"NEW",   "last_name"=>"NEW",];

        $form_state->setValue("names",$names);
        $form_state->setRebuild(true);
    }


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



}

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.