mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW Many-many relation data editing in GridFieldDetailForm
Also adds GridFieldDetailForm->setFields() for customizing the displayed form fields (required for adding relational fields).
This commit is contained in:
parent
46dc8ae1e5
commit
c8136f5d4c
@ -78,7 +78,7 @@ We will now move onto what the `GridFieldConfig`s are and how to use them.
|
||||
|
||||
----
|
||||
|
||||
## GridFieldConfig
|
||||
## Configuration
|
||||
|
||||
A gridfields's behaviour and look all depends on what config we're giving it. In the above example
|
||||
we did not specify one, so it picked a default config called `GridFieldConfig_Base`.
|
||||
@ -98,7 +98,7 @@ A config object can be either injected as the fourth argument of the GridField c
|
||||
|
||||
The framework comes shipped with some base GridFieldConfigs:
|
||||
|
||||
### GridFieldConfig_Base
|
||||
### Table listing with GridFieldConfig_Base
|
||||
|
||||
A simple read-only and paginated view of records with sortable and searchable headers.
|
||||
|
||||
@ -107,7 +107,7 @@ A simple read-only and paginated view of records with sortable and searchable he
|
||||
|
||||
The fields displayed are from `DataObject::getSummaryFields()`
|
||||
|
||||
### GridFieldConfig_RecordViewer
|
||||
### Viewing records with GridFieldConfig_RecordViewer
|
||||
|
||||
Similar to `GridFieldConfig_Base` with the addition support of:
|
||||
|
||||
@ -118,7 +118,7 @@ The fields displayed in the read-only view is from `DataObject::getCMSFields()`
|
||||
:::php
|
||||
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_RecordViewer::create());
|
||||
|
||||
### GridFieldConfig_RecordEditor
|
||||
### Editing records with GridFieldConfig_RecordEditor
|
||||
|
||||
Similar to `GridFieldConfig_RecordViewer` with the addition support of:
|
||||
|
||||
@ -130,7 +130,7 @@ Similar to `GridFieldConfig_RecordViewer` with the addition support of:
|
||||
|
||||
The fields displayed in the edit form are from `DataObject::getCMSFields()`
|
||||
|
||||
### GridFieldConfig_RelationEditor
|
||||
### Editing relations with GridFieldConfig_RelationEditor
|
||||
|
||||
Similar to `GridFieldConfig_RecordEditor`, but adds features to work on a record's has-many or
|
||||
many-many relationships. As such, it expects the list used with the `GridField` to be a
|
||||
@ -147,9 +147,61 @@ The relations can be:
|
||||
|
||||
The fields displayed in the edit form are from `DataObject::getCMSFields()`
|
||||
|
||||
## Customizing Detail Forms
|
||||
|
||||
The `GridFieldDetailForm` component drives the record editing form which is usually configured
|
||||
through the configs `GridFieldConfig_RecordEditor` and `GridFieldConfig_RelationEditor`
|
||||
described above. It takes its fields from `DataObject->getCMSFields()`,
|
||||
but can be customized to accept different fields via its `[api:GridFieldDetailForm->setFields()](api:setFields())` method.
|
||||
|
||||
The component also has the ability to load and save data stored on join tables
|
||||
when two records are related via a "many_many" relationship, as defined through
|
||||
`[api:DataObject::$many_many_extraFields]`. While loading and saving works transparently,
|
||||
you need to add the necessary fields manually, they're not included in the `getCMSFields()` scaffolding.
|
||||
|
||||
These extra fields act like usual form fields, but need to be "namespaced"
|
||||
in order for the gridfield logic to detect them as fields for relation extradata,
|
||||
and to avoid clashes with the other form fields.
|
||||
The namespace notation is `ManyMany[<extradata-field-name>]`, so for example
|
||||
`ManyMany[MyExtraField]`.
|
||||
|
||||
Example:
|
||||
|
||||
:::php
|
||||
class Player extends DataObject {
|
||||
public static $db = array('Name' => 'Text');
|
||||
public static $many_many = array('Teams' => 'Team');
|
||||
public static $many_many_extraFields = array(
|
||||
'Teams' => array('Position' => 'Text')
|
||||
);
|
||||
public function getCMSFields() {
|
||||
$fields = parent::getCMSFields();
|
||||
|
||||
if($this->ID) {
|
||||
$teamFields = singleton('Team')->getCMSFields();
|
||||
$teamFields->addFieldToTab(
|
||||
'Root.Main',
|
||||
// Please follow the "ManyMany[<extradata-name>]" convention
|
||||
new TextField('ManyMany[Position]', 'Current Position')
|
||||
);
|
||||
$config = GridFieldConfig_RelationEditor::create();
|
||||
$config->getComponentByType('GridFieldDetailForm')->setFields($teamFields);
|
||||
$gridField = new GridField('Teams', 'Teams', $this->Teams(), $config);
|
||||
$fields->findOrMakeTab('Root.Teams')->replaceField('Teams', $gridField);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
|
||||
class Team extends DataObject {
|
||||
public static $db = array('Name' => 'Text');
|
||||
public static $many_many = array('Players' => 'Player');
|
||||
}
|
||||
|
||||
## GridFieldComponents
|
||||
|
||||
GridFieldComponents the actual workers in a gridfield. They can be responsible for:
|
||||
The `GridFieldComponent` classes are the actual workers in a gridfield. They can be responsible for:
|
||||
|
||||
- Output some HTML to be rendered
|
||||
- Manipulate data
|
||||
|
@ -152,6 +152,8 @@ Consider replacing it with a more powerful interface in case you have many recor
|
||||
Has-many and many-many relationships are usually handled via the `[GridField](/reference/grid-field)` class,
|
||||
more specifically the `[api:GridFieldAddExistingAutocompleter]` and `[api:GridFieldRelationDelete]` components.
|
||||
They provide a list/detail interface within a single record edited in your ModelAdmin.
|
||||
The `[GridField](/reference/grid-field)` docs also explain how to manage
|
||||
extra relation fields on join tables through its detail forms.
|
||||
|
||||
## Permissions
|
||||
|
||||
|
@ -28,6 +28,11 @@ class GridFieldDetailForm implements GridField_URLHandler {
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @var FieldList Falls back to {@link DataObject->getCMSFields()} if not defined.
|
||||
*/
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* @var String
|
||||
*/
|
||||
@ -127,6 +132,21 @@ class GridFieldDetailForm implements GridField_URLHandler {
|
||||
return $this->validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FieldList $fields
|
||||
*/
|
||||
public function setFields(FieldList $fields) {
|
||||
$this->fields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FieldList
|
||||
*/
|
||||
public function getFields() {
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String
|
||||
*/
|
||||
@ -281,6 +301,8 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
||||
* @return Form
|
||||
*/
|
||||
public function ItemEditForm() {
|
||||
$list = $this->gridField->getList();
|
||||
|
||||
if (empty($this->record)) {
|
||||
$controller = Controller::curr();
|
||||
$noActionURL = $controller->removeAction($_REQUEST['url']);
|
||||
@ -319,16 +341,25 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
||||
$actions->push(new LiteralField('cancelbutton', $text));
|
||||
}
|
||||
}
|
||||
$fields = $this->component->getFields();
|
||||
if(!$fields) $fields = $this->record->getCMSFields();
|
||||
$form = new Form(
|
||||
$this,
|
||||
'ItemEditForm',
|
||||
$this->record->getCMSFields(),
|
||||
$fields,
|
||||
$actions,
|
||||
$this->component->getValidator()
|
||||
);
|
||||
|
||||
$form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT);
|
||||
|
||||
// Load many_many extraData for record.
|
||||
// Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields().
|
||||
if($list instanceof ManyManyList) {
|
||||
$extraData = $list->getExtraData('', $this->record->ID);
|
||||
$form->loadDataFrom(array('ManyMany' => $extraData));
|
||||
}
|
||||
|
||||
// TODO Coupling with CMS
|
||||
$toplevelController = $this->getToplevelController();
|
||||
if($toplevelController && $toplevelController instanceof LeftAndMain) {
|
||||
@ -389,11 +420,19 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
||||
public function doSave($data, $form) {
|
||||
$new_record = $this->record->ID == 0;
|
||||
$controller = Controller::curr();
|
||||
$list = $this->gridField->getList();
|
||||
|
||||
if($list instanceof ManyManyList) {
|
||||
// Data is escaped in ManyManyList->add()
|
||||
$extraData = (isset($data['ManyMany'])) ? $data['ManyMany'] : null;
|
||||
} else {
|
||||
$extraData = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$form->saveInto($this->record);
|
||||
$this->record->write();
|
||||
$this->gridField->getList()->add($this->record);
|
||||
$list->add($this->record, $extraData);
|
||||
} catch(ValidationException $e) {
|
||||
$form->sessionMessage($e->getResult()->message(), 'bad');
|
||||
$responseNegotiator = new PjaxResponseNegotiator(array(
|
||||
|
@ -106,6 +106,45 @@ class GridFieldDetailFormTest extends FunctionalTest {
|
||||
$this->assertDOSContains(array(array('Surname' => 'Baggins')), $group->People());
|
||||
}
|
||||
|
||||
public function testEditFormWithManyManyExtraData() {
|
||||
$this->logInWithPermission('ADMIN');
|
||||
|
||||
// Lists all categories for a person
|
||||
$response = $this->get('GridFieldDetailFormTest_CategoryController');
|
||||
$this->assertFalse($response->isError());
|
||||
$parser = new CSSContentParser($response->getBody());
|
||||
$editlinkitem = $parser->getBySelector('.ss-gridfield-items .first .edit-link');
|
||||
$editlink = (string) $editlinkitem[0]['href'];
|
||||
|
||||
// Edit a single category, incl. manymany extrafields added manually
|
||||
// through GridFieldDetailFormTest_CategoryController
|
||||
$response = $this->get($editlink);
|
||||
$this->assertFalse($response->isError());
|
||||
$parser = new CSSContentParser($response->getBody());
|
||||
$editform = $parser->getBySelector('#Form_ItemEditForm');
|
||||
$editformurl = (string) $editform[0]['action'];
|
||||
|
||||
$manyManyField = $parser->getByXpath('//*[@id="Form_ItemEditForm"]//input[@name="ManyMany[IsPublished]"]');
|
||||
$this->assertTrue((bool)$manyManyField);
|
||||
|
||||
$response = $this->post(
|
||||
$editformurl,
|
||||
array(
|
||||
'Name' => 'Updated Category',
|
||||
'ManyMany' => array('IsPublished' => 1),
|
||||
'action_doSave' => 1
|
||||
)
|
||||
);
|
||||
$this->assertFalse($response->isError());
|
||||
|
||||
$person = GridFieldDetailFormTest_Person::get()->sort('FirstName')->First();
|
||||
$category = $person->Categories()->filter(array('Name' => 'Updated Category'))->First();
|
||||
$this->assertEquals(
|
||||
array('IsPublished' => 1),
|
||||
$person->Categories()->getExtraData('', $category->ID)
|
||||
);
|
||||
}
|
||||
|
||||
public function testNestedEditForm() {
|
||||
$this->logInWithPermission('ADMIN');
|
||||
|
||||
@ -193,6 +232,12 @@ class GridFieldDetailFormTest_Person extends DataObject implements TestOnly {
|
||||
'Categories' => 'GridFieldDetailFormTest_Category'
|
||||
);
|
||||
|
||||
static $many_many_extraFields = array(
|
||||
'Categories' => array(
|
||||
'IsPublished' => 'Boolean'
|
||||
)
|
||||
);
|
||||
|
||||
static $default_sort = 'FirstName';
|
||||
|
||||
public function getCMSFields() {
|
||||
@ -289,5 +334,23 @@ class GridFieldDetailFormTest_GroupController extends Controller implements Test
|
||||
}
|
||||
}
|
||||
|
||||
class GridFieldDetailFormTest_CategoryController extends Controller implements TestOnly {
|
||||
protected $template = 'BlankPage';
|
||||
|
||||
public function Form() {
|
||||
// GridField lists categories for a specific person
|
||||
$person = GridFieldDetailFormTest_Person::get()->sort('FirstName')->First();
|
||||
$detailFields = singleton('GridFieldDetailFormTest_Category')->getCMSFields();
|
||||
$detailFields->addFieldToTab('Root.Main', new CheckboxField('ManyMany[IsPublished]'));
|
||||
$field = new GridField('testfield', 'testfield', $person->Categories());
|
||||
$field->getConfig()->addComponent($gridFieldForm = new GridFieldDetailForm($this, 'Form'));
|
||||
$gridFieldForm->setFields($detailFields);
|
||||
$field->getConfig()->addComponent(new GridFieldToolbarHeader());
|
||||
$field->getConfig()->addComponent(new GridFieldAddNewButton('toolbar-header-right'));
|
||||
$field->getConfig()->addComponent(new GridFieldEditButton());
|
||||
return new Form($this, 'Form', new FieldList($field), new FieldList());
|
||||
}
|
||||
}
|
||||
|
||||
class GridFieldDetailFormTest_ItemRequest extends GridFieldDetailForm_ItemRequest implements TestOnly {
|
||||
}
|
Loading…
Reference in New Issue
Block a user