Issue 3357 - Add GridField Readonly Transformation

GridField doesn't have a valid readonly state if it's value is set to an Object
without `forTemplate()`. The default behaviour is to render a ReadonlyField,
but given GridField is a complex type this isn't suitable.

This bugfix provides a transformation method to render only components that are
whitelisted to provide a readonly state.

@see #3357 - https://github.com/silverstripe/silverstripe-framework/issues/3357
This commit is contained in:
Simon Gow 2018-08-29 14:34:21 +12:00
parent 37a266f2f0
commit 4c3a068859
3 changed files with 183 additions and 0 deletions

View File

@ -105,6 +105,21 @@ class GridField extends FormField
*/
protected $name = '';
/**
* A whitelist of readonly component classes allowed if performReadonlyTransform is called.
*
* @var array
*/
protected $readonlyComponents = array(
GridFieldDetailForm::class,
GridFieldDataColumns::class,
GridFieldConfig_RecordViewer::class,
GridFieldToolbarHeader::class,
GridFieldPageCount::class,
GridFieldPaginator::class,
GridState_Component::class
);
/**
* Pattern used for looking up
*/
@ -193,6 +208,60 @@ class GridField extends FormField
);
}
/**
* Overload the readonly components for this gridfield.
*
* @param array $components an array map of component class references to whitelist for a readonly version.
*/
public function setReadonlyComponents(array $components)
{
$this->readonlyComponents = $components;
}
/**
* Return the readonly components
*
* @return array a map of component classes.
*/
public function getReadonlyComponents()
{
return $this->readonlyComponents;
}
/**
* Custom Readonly transformation to remove actions which shouldn't be present for a readonly state.
*
* @return GridField
*/
public function performReadonlyTransformation()
{
$copy = clone $this;
$copy->setReadonly(true);
// get the whitelist for allowable readonly components
$allowedComponents = $this->getReadonlyComponents();
foreach ($this->getConfig()->getComponents() as $component) {
// if a component doesn't exist, remove it from the readonly version.
if (!in_array(get_class($component), $allowedComponents)) {
$copy->getConfig()->removeComponent($component);
}
}
return $copy;
}
/**
* Disabling the gridfield should have the same affect as making it readonly (removing all action items).
*
* @return GridField
*/
public function performDisabledTransformation(){
parent::performDisabledTransformation();
return $this->performReadonlyTransformation();
}
/**
* @return GridFieldConfig
*/

View File

@ -0,0 +1,87 @@
<?php
namespace SilverStripe\Forms\Tests\GridField;
use SilverStripe\Forms\GridField\GridField_ActionMenu;
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
use SilverStripe\Forms\GridField\GridFieldAddNewButton;
use SilverStripe\Forms\GridField\GridFieldButtonRow;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
use SilverStripe\Forms\GridField\GridFieldDetailForm;
use SilverStripe\Forms\GridField\GridFieldEditButton;
use SilverStripe\Forms\GridField\GridFieldFilterHeader;
use SilverStripe\Forms\GridField\GridFieldPageCount;
use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Versioned\VersionedGridFieldState\VersionedGridFieldState;
class GridFieldReadonlyTest extends SapphireTest
{
protected static $fixture_file = 'GridFieldReadonlyTest.yml';
protected static $extra_dataobjects = array(
Team::class,
Cheerleader::class,
);
/**
* The CMS can set the value of a GridField to be a hasMany relation, which needs a readonly state.
* This test ensures GridField has a readonly transformation.
*/
public function testReadOnlyTransformation()
{
// Build a hasMany Relation via getComponents like ModelAdmin does.
$components = Team::get_one(Team::class)
->getComponents('Cheerleaders');
$gridConfig = GridFieldConfig_RelationEditor::create();
// Build some commonly used components to make sure we're only allowing the correct components
$gridConfig->addComponent(new GridFieldButtonRow('before'));
$gridConfig->addComponent(new GridFieldAddNewButton('buttons-before-left'));
$gridConfig->addComponent(new GridFieldAddExistingAutocompleter('buttons-before-right'));
$gridConfig->addComponent(new GridFieldToolbarHeader());
$gridConfig->addComponent($sort = new GridFieldSortableHeader());
$gridConfig->addComponent($filter = new GridFieldFilterHeader());
$gridConfig->addComponent(new GridFieldDataColumns());
$gridConfig->addComponent(new GridFieldEditButton());
$gridConfig->addComponent(new GridFieldDeleteAction(true));
$gridConfig->addComponent(new GridField_ActionMenu());
$gridConfig->addComponent(new GridFieldPageCount('toolbar-header-right'));
$gridConfig->addComponent($pagination = new GridFieldPaginator(2));
$gridConfig->addComponent(new GridFieldDetailForm());
$gridConfig->addComponent(new GridFieldDeleteAction());
$gridConfig->addComponent(new VersionedGridFieldState());
$gridField = GridField::create(
'Cheerleaders',
'Cheerleaders',
$components,
$gridConfig
);
// Model Admin sets the value of the GridField directly to the relation, which doesn't have a forTemplate()
// function, if we rely on FormField to render into a ReadonlyField we'll get an error as HasManyRelation
// doesn't have a forTemplate() function.
$gridField->setValue($components);
$gridField->setModelClass(Cheerleader::class);
// This function is called by $form->makeReadonly().
$readonlyGridField = $gridField->performReadonlyTransformation();
// if we've made it this far, then the GridField is at least transforming correctly.
$readonlyComponents = $readonlyGridField->getReadonlyComponents();
// assert that all the components in the readonly version are present in the whitelist.
foreach($readonlyGridField->getConfig()->getComponents() as $component){
$this->assertTrue(in_array(get_class($component), $readonlyComponents));
}
}
}

View File

@ -0,0 +1,27 @@
SilverStripe\Forms\Tests\GridField\GridFieldTest\Team:
team1:
Name: Team 1
City: Cologne
team2:
Name: Team 2
City: Wellington
team3:
Name: Team 3
City: Auckland
team4:
Name: Team 4
City: Melbourne
SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader:
cheerleader1:
Name: Heather
Team: =>SilverStripe\Forms\Tests\GridField\GridFieldTest\Team.team1
cheerleader2:
Name: Bob
Team: =>SilverStripe\Forms\Tests\GridField\GridFieldTest\Team.team1
cheerleader3:
Name: Jenny
Team: =>SilverStripe\Forms\Tests\GridField\GridFieldTest\Team.team1
cheerleader4:
Name: Sam
Team: =>SilverStripe\Forms\Tests\GridField\GridFieldTest\Team.team1