mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW Allow DataObject classes to define scaffolded relation formfields (#11269)
This commit is contained in:
parent
64ac096b46
commit
be0eab2bae
@ -37,7 +37,7 @@
|
||||
"psr/http-message": "^1",
|
||||
"sebastian/diff": "^4.0",
|
||||
"silverstripe/config": "^2",
|
||||
"silverstripe/assets": "^2.2",
|
||||
"silverstripe/assets": "^2.3",
|
||||
"silverstripe/vendor-plugin": "^2",
|
||||
"sminnee/callbacklist": "^0.1.1",
|
||||
"symfony/cache": "^6.1",
|
||||
|
@ -138,27 +138,37 @@ class FormScaffolder
|
||||
&& ($this->includeRelations === true || isset($this->includeRelations['has_many']))
|
||||
) {
|
||||
foreach ($this->obj->hasMany() as $relationship => $component) {
|
||||
if ($this->tabbed) {
|
||||
$fields->findOrMakeTab(
|
||||
"Root.$relationship",
|
||||
$this->obj->fieldLabel($relationship)
|
||||
);
|
||||
}
|
||||
$includeInOwnTab = true;
|
||||
$fieldLabel = $this->obj->fieldLabel($relationship);
|
||||
$fieldClass = (isset($this->fieldClasses[$relationship]))
|
||||
? $this->fieldClasses[$relationship]
|
||||
: 'SilverStripe\\Forms\\GridField\\GridField';
|
||||
/** @var GridField $grid */
|
||||
$grid = Injector::inst()->create(
|
||||
$fieldClass,
|
||||
$relationship,
|
||||
$this->obj->fieldLabel($relationship),
|
||||
$this->obj->$relationship(),
|
||||
GridFieldConfig_RelationEditor::create()
|
||||
);
|
||||
if ($this->tabbed) {
|
||||
$fields->addFieldToTab("Root.$relationship", $grid);
|
||||
: null;
|
||||
if ($fieldClass) {
|
||||
/** @var GridField */
|
||||
$hasManyField = Injector::inst()->create(
|
||||
$fieldClass,
|
||||
$relationship,
|
||||
$fieldLabel,
|
||||
$this->obj->$relationship(),
|
||||
GridFieldConfig_RelationEditor::create()
|
||||
);
|
||||
} else {
|
||||
$fields->push($grid);
|
||||
/** @var DataObject */
|
||||
$hasManySingleton = singleton($component);
|
||||
$hasManyField = $hasManySingleton->scaffoldFormFieldForHasMany($relationship, $fieldLabel, $this->obj, $includeInOwnTab);
|
||||
}
|
||||
if ($this->tabbed) {
|
||||
if ($includeInOwnTab) {
|
||||
$fields->findOrMakeTab(
|
||||
"Root.$relationship",
|
||||
$fieldLabel
|
||||
);
|
||||
$fields->addFieldToTab("Root.$relationship", $hasManyField);
|
||||
} else {
|
||||
$fields->addFieldToTab('Root.Main', $hasManyField);
|
||||
}
|
||||
} else {
|
||||
$fields->push($hasManyField);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -187,7 +197,7 @@ class FormScaffolder
|
||||
*
|
||||
* @param FieldList $fields Reference to the @FieldList to add fields to.
|
||||
* @param string $relationship The relationship identifier.
|
||||
* @param mixed $overrideFieldClass Specify the field class to use here or leave as null to use default.
|
||||
* @param string|null $overrideFieldClass Specify the field class to use here or leave as null to use default.
|
||||
* @param bool $tabbed Whether this relationship has it's own tab or not.
|
||||
* @param DataObject $dataObject The @DataObject that has the relation.
|
||||
*/
|
||||
@ -198,28 +208,37 @@ class FormScaffolder
|
||||
$tabbed,
|
||||
DataObject $dataObject
|
||||
) {
|
||||
if ($tabbed) {
|
||||
$fields->findOrMakeTab(
|
||||
"Root.$relationship",
|
||||
$dataObject->fieldLabel($relationship)
|
||||
$includeInOwnTab = true;
|
||||
$fieldLabel = $dataObject->fieldLabel($relationship);
|
||||
|
||||
if ($overrideFieldClass) {
|
||||
/** @var GridField */
|
||||
$manyManyField = Injector::inst()->create(
|
||||
$overrideFieldClass,
|
||||
$relationship,
|
||||
$fieldLabel,
|
||||
$dataObject->$relationship(),
|
||||
GridFieldConfig_RelationEditor::create()
|
||||
);
|
||||
} else {
|
||||
$manyManyComponent = DataObject::getSchema()->manyManyComponent(get_class($dataObject), $relationship);
|
||||
/** @var DataObject */
|
||||
$manyManySingleton = singleton($manyManyComponent['childClass']);
|
||||
$manyManyField = $manyManySingleton->scaffoldFormFieldForManyMany($relationship, $fieldLabel, $dataObject, $includeInOwnTab);
|
||||
}
|
||||
|
||||
$fieldClass = $overrideFieldClass ?: GridField::class;
|
||||
|
||||
/** @var GridField $grid */
|
||||
$grid = Injector::inst()->create(
|
||||
$fieldClass,
|
||||
$relationship,
|
||||
$dataObject->fieldLabel($relationship),
|
||||
$dataObject->$relationship(),
|
||||
GridFieldConfig_RelationEditor::create()
|
||||
);
|
||||
|
||||
if ($tabbed) {
|
||||
$fields->addFieldToTab("Root.$relationship", $grid);
|
||||
if ($includeInOwnTab) {
|
||||
$fields->findOrMakeTab(
|
||||
"Root.$relationship",
|
||||
$fieldLabel
|
||||
);
|
||||
$fields->addFieldToTab("Root.$relationship", $manyManyField);
|
||||
} else {
|
||||
$fields->addFieldToTab('Root.Main', $manyManyField);
|
||||
}
|
||||
} else {
|
||||
$fields->push($grid);
|
||||
$fields->push($manyManyField);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,10 @@ use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\FormScaffolder;
|
||||
use SilverStripe\Forms\CompositeValidator;
|
||||
use SilverStripe\Forms\FieldsValidator;
|
||||
use SilverStripe\Forms\GridField\GridField;
|
||||
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
|
||||
use SilverStripe\Forms\HiddenField;
|
||||
use SilverStripe\Forms\SearchableDropdownField;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\i18n\i18nEntityProvider;
|
||||
use SilverStripe\ORM\Connect\MySQLSchemaManager;
|
||||
@ -26,6 +29,7 @@ use SilverStripe\ORM\FieldType\DBComposite;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\FieldType\DBEnum;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\ORM\FieldType\DBForeignKey;
|
||||
use SilverStripe\ORM\Filters\PartialMatchFilter;
|
||||
use SilverStripe\ORM\Filters\SearchFilter;
|
||||
use SilverStripe\ORM\Queries\SQLDelete;
|
||||
@ -2485,6 +2489,70 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $fs->getFieldList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scaffold a form field for selecting records of this model type in a has_one relation.
|
||||
*
|
||||
* @param string $fieldName The name we usually expect the field to have. This is often the has_one relation
|
||||
* name with "ID" suffixed to it.
|
||||
* @param string $relationName The name of the actual has_one relation, without "ID" suffixed to it.
|
||||
* Some form fields such as UploadField use this instead of the usual field name.
|
||||
*/
|
||||
public function scaffoldFormFieldForHasOne(
|
||||
string $fieldName,
|
||||
?string $fieldTitle,
|
||||
string $relationName,
|
||||
DataObject $ownerRecord
|
||||
): FormField {
|
||||
$labelField = $this->hasField('Title') ? 'Title' : 'Name';
|
||||
$list = DataList::create(static::class);
|
||||
$threshold = DBForeignKey::config()->get('dropdown_field_threshold');
|
||||
$overThreshold = $list->count() > $threshold;
|
||||
$field = SearchableDropdownField::create($fieldName, $fieldTitle, $list, $labelField)
|
||||
->setIsLazyLoaded($overThreshold)
|
||||
->setLazyLoadLimit($threshold);
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scaffold a form field for selecting records of this model type in a has_many relation.
|
||||
*
|
||||
* @param bool &$includeInTab Set this to true if the field should be in its own tab. False otherwise.
|
||||
*/
|
||||
public function scaffoldFormFieldForHasMany(
|
||||
string $relationName,
|
||||
?string $fieldTitle,
|
||||
DataObject $ownerRecord,
|
||||
bool &$includeInOwnTab
|
||||
): FormField {
|
||||
$includeInOwnTab = true;
|
||||
return GridField::create(
|
||||
$relationName,
|
||||
$fieldTitle,
|
||||
$ownerRecord->$relationName(),
|
||||
GridFieldConfig_RelationEditor::create()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scaffold a form field for selecting records of this model type in a many_many relation.
|
||||
*
|
||||
* @param bool &$includeInTab Set this to true if the field should be in its own tab. False otherwise.
|
||||
*/
|
||||
public function scaffoldFormFieldForManyMany(
|
||||
string $relationName,
|
||||
?string $fieldTitle,
|
||||
DataObject $ownerRecord,
|
||||
bool &$includeInOwnTab
|
||||
): FormField {
|
||||
$includeInOwnTab = true;
|
||||
return GridField::create(
|
||||
$relationName,
|
||||
$fieldTitle,
|
||||
$ownerRecord->$relationName(),
|
||||
GridFieldConfig_RelationEditor::create()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows user code to hook into DataObject::getCMSFields prior to updateCMSFields
|
||||
* being called on extensions
|
||||
|
@ -28,7 +28,8 @@ class DBForeignKey extends DBInt
|
||||
protected $object;
|
||||
|
||||
/**
|
||||
* Number of related objects to show in a dropdown before it switches to using lazyloading
|
||||
* Number of related objects to show in a scaffolded searchable dropdown field before it
|
||||
* switches to using lazyloading.
|
||||
* This will also be used as the lazy load limit
|
||||
*
|
||||
* @config
|
||||
@ -65,23 +66,7 @@ class DBForeignKey extends DBInt
|
||||
return null;
|
||||
}
|
||||
$hasOneSingleton = singleton($hasOneClass);
|
||||
if ($hasOneSingleton instanceof File) {
|
||||
$field = Injector::inst()->create(FileHandleField::class, $relationName, $title);
|
||||
if ($hasOneSingleton instanceof Image) {
|
||||
$field->setAllowedFileCategories('image/supported');
|
||||
}
|
||||
if ($field->hasMethod('setAllowedMaxFileNumber')) {
|
||||
$field->setAllowedMaxFileNumber(1);
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
$labelField = $hasOneSingleton->hasField('Title') ? 'Title' : 'Name';
|
||||
$list = DataList::create($hasOneClass);
|
||||
$threshold = self::config()->get('dropdown_field_threshold');
|
||||
$overThreshold = $list->count() > $threshold;
|
||||
$field = SearchableDropdownField::create($this->name, $title, $list, $labelField)
|
||||
->setIsLazyLoaded($overThreshold)
|
||||
->setLazyLoadLimit($threshold);
|
||||
$field = $hasOneSingleton->scaffoldFormFieldForHasOne($this->name, $title, $relationName, $this->object);
|
||||
return $field;
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,18 @@ namespace SilverStripe\Forms\Tests;
|
||||
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Forms\CurrencyField;
|
||||
use SilverStripe\Forms\DateField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\Tests\FormScaffolderTest\Article;
|
||||
use SilverStripe\Forms\Tests\FormScaffolderTest\ArticleExtension;
|
||||
use SilverStripe\Forms\Tests\FormScaffolderTest\Author;
|
||||
use SilverStripe\Forms\Tests\FormScaffolderTest\Child;
|
||||
use SilverStripe\Forms\Tests\FormScaffolderTest\ParentModel;
|
||||
use SilverStripe\Forms\Tests\FormScaffolderTest\ParentChildJoin;
|
||||
use SilverStripe\Forms\Tests\FormScaffolderTest\Tag;
|
||||
use SilverStripe\Forms\TimeField;
|
||||
|
||||
/**
|
||||
* Tests for DataObject FormField scaffolding
|
||||
@ -30,9 +36,11 @@ class FormScaffolderTest extends SapphireTest
|
||||
Article::class,
|
||||
Tag::class,
|
||||
Author::class,
|
||||
ParentModel::class,
|
||||
Child::class,
|
||||
ParentChildJoin::class,
|
||||
];
|
||||
|
||||
|
||||
public function testGetCMSFieldsSingleton()
|
||||
{
|
||||
$article = new Article;
|
||||
@ -162,4 +170,47 @@ class FormScaffolderTest extends SapphireTest
|
||||
|
||||
$this->assertFalse($fields->hasTabSet(), 'getFrontEndFields() doesnt produce a TabSet by default');
|
||||
}
|
||||
|
||||
public function provideScaffoldRelationFormFields()
|
||||
{
|
||||
return [
|
||||
[true],
|
||||
[false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideScaffoldRelationFormFields
|
||||
*/
|
||||
public function testScaffoldRelationFormFields(bool $includeInOwnTab)
|
||||
{
|
||||
$parent = $this->objFromFixture(ParentModel::class, 'parent1');
|
||||
Child::$includeInOwnTab = $includeInOwnTab;
|
||||
$fields = $parent->scaffoldFormFields(['includeRelations' => true, 'tabbed' => true]);
|
||||
|
||||
foreach (array_keys(ParentModel::config()->uninherited('has_one')) as $hasOneName) {
|
||||
$scaffoldedFormField = $fields->dataFieldByName($hasOneName . 'ID');
|
||||
if ($hasOneName === 'ChildPolymorphic') {
|
||||
$this->assertNull($scaffoldedFormField, "$hasOneName should be null");
|
||||
} else {
|
||||
$this->assertInstanceOf(DateField::class, $scaffoldedFormField, "$hasOneName should be a DateField");
|
||||
}
|
||||
}
|
||||
foreach (array_keys(ParentModel::config()->uninherited('has_many')) as $hasManyName) {
|
||||
$this->assertInstanceOf(CurrencyField::class, $fields->dataFieldByName($hasManyName), "$hasManyName should be a CurrencyField");
|
||||
if ($includeInOwnTab) {
|
||||
$this->assertNotNull($fields->findTab("Root.$hasManyName"));
|
||||
} else {
|
||||
$this->assertNull($fields->findTab("Root.$hasManyName"));
|
||||
}
|
||||
}
|
||||
foreach (array_keys(ParentModel::config()->uninherited('many_many')) as $manyManyName) {
|
||||
$this->assertInstanceOf(TimeField::class, $fields->dataFieldByName($manyManyName), "$manyManyName should be a TimeField");
|
||||
if ($includeInOwnTab) {
|
||||
$this->assertNotNull($fields->findTab("Root.$manyManyName"));
|
||||
} else {
|
||||
$this->assertNull($fields->findTab("Root.$manyManyName"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,3 +10,6 @@ SilverStripe\Forms\Tests\FormScaffolderTest\Author:
|
||||
author1:
|
||||
FirstName: Author 1
|
||||
Tags: =>SilverStripe\Forms\Tests\FormScaffolderTest\Article.article1
|
||||
SilverStripe\Forms\Tests\FormScaffolderTest\ParentModel:
|
||||
parent1:
|
||||
Title: Parent 1
|
||||
|
57
tests/php/Forms/FormScaffolderTest/Child.php
Normal file
57
tests/php/Forms/FormScaffolderTest/Child.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Forms\Tests\FormScaffolderTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\Forms\CurrencyField;
|
||||
use SilverStripe\Forms\DateField;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\TimeField;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class Child extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'FormScaffolderTest_Child';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $has_one = [
|
||||
'Parent' => ParentModel::class,
|
||||
];
|
||||
|
||||
public static bool $includeInOwnTab = true;
|
||||
|
||||
public function scaffoldFormFieldForHasOne(
|
||||
string $fieldName,
|
||||
?string $fieldTitle,
|
||||
string $relationName,
|
||||
DataObject $ownerRecord
|
||||
): FormField {
|
||||
// Intentionally return a field that is unlikely to be used by default in the future.
|
||||
return DateField::create($fieldName, $fieldTitle);
|
||||
}
|
||||
|
||||
public function scaffoldFormFieldForHasMany(
|
||||
string $relationName,
|
||||
?string $fieldTitle,
|
||||
DataObject $ownerRecord,
|
||||
bool &$includeInOwnTab
|
||||
): FormField {
|
||||
$includeInOwnTab = static::$includeInOwnTab;
|
||||
// Intentionally return a field that is unlikely to be used by default in the future.
|
||||
return CurrencyField::create($relationName, $fieldTitle);
|
||||
}
|
||||
|
||||
public function scaffoldFormFieldForManyMany(
|
||||
string $relationName,
|
||||
?string $fieldTitle,
|
||||
DataObject $ownerRecord,
|
||||
bool &$includeInOwnTab
|
||||
): FormField {
|
||||
$includeInOwnTab = static::$includeInOwnTab;
|
||||
// Intentionally return a field that is unlikely to be used by default in the future.
|
||||
return TimeField::create($relationName, $fieldTitle);
|
||||
}
|
||||
}
|
20
tests/php/Forms/FormScaffolderTest/ParentChildJoin.php
Normal file
20
tests/php/Forms/FormScaffolderTest/ParentChildJoin.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Forms\Tests\FormScaffolderTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class ParentChildJoin extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'FormScaffolderTest_ParentChildJoin';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $has_one = [
|
||||
'Parent' => ParentModel::class,
|
||||
'Child' => Child::class,
|
||||
];
|
||||
}
|
33
tests/php/Forms/FormScaffolderTest/ParentModel.php
Normal file
33
tests/php/Forms/FormScaffolderTest/ParentModel.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Forms\Tests\FormScaffolderTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class ParentModel extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'FormScaffolderTest_ParentModel';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
|
||||
private static $has_one = [
|
||||
'Child' => Child::class,
|
||||
'ChildPolymorphic' => DataObject::class,
|
||||
];
|
||||
|
||||
private static $has_many = [
|
||||
'ChildrenHasMany' => Child::class . '.Parent',
|
||||
];
|
||||
|
||||
private static $many_many = [
|
||||
'ChildrenManyMany' => Child::class,
|
||||
'ChildrenManyManyThrough' => [
|
||||
'through' => ParentChildJoin::class,
|
||||
'from' => 'Parent',
|
||||
'to' => 'Child',
|
||||
]
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue
Block a user