Skip to content

SilverStripe\Forms\MultiSelectField not saving into many_many_extraFields #11942

@elbroto

Description

@elbroto

Module version(s) affected

6.x, 5.x, 4.x

Description

I wanted to save values from a CheckboxSetField into a many_many_extraFields Text field. Doing this with a standard TextField works fine, but any MultiSelectField fails. The way MultiSelectField->saveInto() is implemented does not work with the ManyMany[Ranking] declaration.
However the ManyMany[]-prefix is required. Without that field being present, in GridFieldDetailForm_ItemRequest->getExtraSavedData() the line $record->hasField($savedField) returns false and no $extraData will be available in GridFieldDetailForm_ItemRequest->saveFormIntoRecord()

I hope this makes sense. I spend a few hours debugging this and my brain is a bit mushy. Please see my sample code. Some breakpoints at the places mentioned above will help as well :)

How to reproduce

  1. Clean Installation.
  2. Use the first code example.
  3. Add a team
  4. Add a Supporter to the team, change some checkboxes and save
  5. Refresh and/or SELECT * FROM Team_Supporters
class TeamModelAdmin extends \SilverStripe\Admin\ModelAdmin {
    private static $url_segment = 'teams';
    private static $managed_models = [
        Team::class,
        Supporter::class,
    ];
}

class Team extends \SilverStripe\ORM\DataObject {
    private static $many_many = [
        'Supporters' => Supporter::class,
    ];

    private static $many_many_extraFields = [
        'Supporters' => [
            'Ranking' => 'Text',
        ],
    ];

    public function getCMSFields() {
        $this->beforeUpdateCMSFields(function ($fields) {
            if (!$this->isInDB())
                return;

            $gfConfig = \SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor::create();
            $detailform = $gfConfig->getComponentByType(\SilverStripe\Forms\GridField\GridFieldDetailForm::class);
            $values = ['a', 'b', 'c', 'd'];
            $values = array_combine($values, $values);

            $detailform->setFields(\SilverStripe\Forms\FieldList::create(
                \SilverStripe\Forms\CheckboxSetField::create('ManyMany[Ranking]', null, $values),
            ));

            $fields->dataFieldByName('Supporters')->setConfig($gfConfig);
        });

        return parent::getCMSFields();
    }
}

class Supporter extends \SilverStripe\ORM\DataObject {}

In order to make it work without core hacking, I had to do the following changes. I would be fine with most of them, but overriding Supporter->__construct() seems a bit hacky.

class Team extends \SilverStripe\ORM\DataObject {

    // [...]

    public function getCMSFields() {
        $this->beforeUpdateCMSFields(function ($fields) {
            if (!$this->isInDB())
                return;

            $gfConfig = \SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor::create();
            $detailform = $gfConfig->getComponentByType(\SilverStripe\Forms\GridField\GridFieldDetailForm::class);
            $values = ['a', 'b', 'c', 'd'];
            $values = array_combine($values, $values);

            $detailform->setFields(\SilverStripe\Forms\FieldList::create(
                \SilverStripe\Forms\CheckboxSetField::create('ManyMany[Ranking]', null, $values),
            ));
            $detailform->setItemEditFormCallback(function (\SilverStripe\Forms\Form $form) {
                $record = $form->getRecord();
                // without the following chain the updated values would be visible only after a page reload
                $valCurrent = $record->getField('ManyMany[Ranking]') ?? $record->getField('Ranking') ?? '[]';
                $field = $form->Fields()->dataFieldByName('ManyMany[Ranking]');
                $field->setValue(json_decode($valCurrent));
            });

            $fields->dataFieldByName('Supporters')->setConfig($gfConfig);
        });

        return parent::getCMSFields();
    }
}

class Supporter extends \SilverStripe\ORM\DataObject {
    public function __construct($record = [], $creationType = self::CREATE_OBJECT, $queryParams = []) {
        parent::__construct($record, $creationType, $queryParams);

        $this->setField('ManyMany[Ranking]', $this->getField('Ranking'));
    }
}

Possible Solution

A better implementation of MultiSelectField->saveInto() / loadFrom() with checking if $fieldName starts with 'ManyMany[' seems to have the least friction?

Additional Context

No response

Validations

  • Check that there isn't already an issue that reports the same bug
  • Double check that your reproduction steps work in a fresh installation of silverstripe/installer (with any code examples you've provided)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions