Merge branch '4' into 5

This commit is contained in:
Guy Sartorelli 2024-06-11 13:18:41 +12:00
commit 66d8c51989
No known key found for this signature in database
GPG Key ID: F313E3B9504D496A
5 changed files with 488 additions and 26 deletions

31
behat.yml Normal file
View File

@ -0,0 +1,31 @@
# Run silverstripe-gridfieldextensions behat tests with this command
# Note that silverstripe-gridfieldextensions behat tests require CMS module
# ========================================================================= #
# chromedriver
# vendor/bin/behat @silverstripe-gridfieldextensions
# ========================================================================= #
default:
suites:
silverstripe-gridfieldextensions:
paths:
- '%paths.modules.silverstripe-gridfieldextensions%/tests/behat/features'
contexts:
- SilverStripe\Framework\Tests\Behaviour\FeatureContext
- SilverStripe\Framework\Tests\Behaviour\CmsFormsContext
- SilverStripe\Framework\Tests\Behaviour\CmsUiContext
- SilverStripe\BehatExtension\Context\BasicContext
- SilverStripe\BehatExtension\Context\LoginContext
- SilverStripe\BehatExtension\Context\FixtureContext:
- '%paths.modules.silverstripe-gridfieldextensions%/tests/behat/features/files/'
extensions:
SilverStripe\BehatExtension\MinkExtension:
default_session: facebook_web_driver
javascript_session: facebook_web_driver
facebook_web_driver:
browser: chrome
wd_host: "http://127.0.0.1:9515" #chromedriver port
SilverStripe\BehatExtension\Extension:
screenshot_path: '%paths.base%/artifacts/screenshots'
bootstrap_file: vendor/silverstripe/framework/tests/behat/serve-bootstrap.php

View File

@ -34,7 +34,8 @@
"squizlabs/php_codesniffer": "^3.7", "squizlabs/php_codesniffer": "^3.7",
"silverstripe/versioned": "^3", "silverstripe/versioned": "^3",
"silverstripe/standards": "^1", "silverstripe/standards": "^1",
"phpstan/extension-installer": "^1.3" "phpstan/extension-installer": "^1.3",
"silverstripe/frameworktest": "^2"
}, },
"extra": { "extra": {
"screenshots": [ "screenshots": [

View File

@ -159,39 +159,265 @@ Nested GridFields
The `GridFieldNestedForm` component allows you to nest GridFields in the UI. It can be used with `DataObject` subclasses The `GridFieldNestedForm` component allows you to nest GridFields in the UI. It can be used with `DataObject` subclasses
with the `Hierarchy` extension, or by specifying the relation used for nesting. with the `Hierarchy` extension, or by specifying the relation used for nesting.
```php Here is a small example of basic use of Nested GridField.
// Basic usage, defaults to the Children-method for Hierarchy objects.
$grid->getConfig()->addComponent(GridFieldNestedForm::create());
// Usage with custom relation
$grid->getConfig()->addComponent(GridFieldNestedForm::create()->setRelationName('MyRelation'));
```
You can define your own custom GridField config for the nested GridField configuration by implementing a `getNestedConfig`
on your nested model (should return a `GridField_Config` object).
```php ```php
class NestedObject extends DataObject namespace App\Admin;
use MyObject;
use SilverStripe\Admin\ModelAdmin;
use Symbiote\GridFieldExtensions\GridFieldNestedForm;
class MyAdmin extends ModelAdmin
{ {
private static $has_one = [ //...
'Parent' => ParentObject::class
];
public function getNestedConfig(): GridFieldConfig public function getEditForm($id = null, $fields = null)
{ {
$config = new GridFieldConfig_RecordViewer(); $form = parent::getEditForm($id, $fields);
return $config;
} $grid = $form->Fields()->dataFieldByName(MyObject::class);
// Add Nested GridField to main GridField
$grid->getConfig()->addComponent(GridFieldNestedForm::create());
return $form;
}
} }
``` ```
You can also modify the default config (a `GridFieldConfig_RecordEditor`) via an extension to the nested model class, by implementing There are several ways to use Nested GridField. The implementation depends on the type of data you will be using in your GridField.
`updateNestedConfig`, which will get the config object as the first parameter. For instance, if there is a `DataObject` that has the `Hierarchy` extension, you can use the following approach.
As an example, here we can use a typical hierarchy of the `Group` model, where another `Group` can serve as a parent.
```php ```php
class NestedObjectExtension extends DataExtension namespace App\Extensions;
use SilverStripe\Core\Extension;
use SilverStripe\Security\Group;
use Symbiote\GridFieldExtensions\GridFieldNestedForm;
class MySecurityAdminExtension extends Extension
{ {
public function updateNestedConfig(GridFieldConfig &$config) public function updateGridFieldConfig($config)
{ {
$config->removeComponentsByType(GridFieldPaginator::class); if ($this->owner->getModelClass() === Group::class) {
} $config->addComponent(GridFieldNestedForm::create());
}
}
} }
``` ```
Or also view a list of all members of a given group. Notice we call `setRelationName()` to explicitly state the relation which should be displayed in the Nested GridField.
```php
namespace App\Extensions;
use SilverStripe\Core\Extension;
use SilverStripe\Security\Group;
use Symbiote\GridFieldExtensions\GridFieldNestedForm;
class MySecurityAdminExtension extends Extension
{
public function updateGridFieldConfig($config)
{
if ($this->owner->getModelClass() === Group::class) {
$config->addComponent(GridFieldNestedForm::create()->setRelationName('Members'));
}
}
}
```
```yml
SilverStripe\Admin\SecurityAdmin:
extensions:
- App\Extensions\MySecurityAdminExtension
```
Another way to use Nested GridField together with `DataObjects` that do not have the `Hierarchy` extension but have `has_many` relationships with other objects.
Let's say you have the following `DataObject` that has multiple levels of relationships, and an admin section where the data of this object will be displayed.
You want the user to be able to view information regarding this object and all nested objects on the same page, without the need to navigate to each object individually.
In this case, you can use the following approach.
```php
namespace App\Models;
use SilverStripe\ORM\DataObject;
class ParentNode extends DataObject
{
//...
private static $has_many = [
'ChildNodes' => BranchNode::class,
];
}
```
You can define your own custom GridField config for the nested GridField configuration by implementing a `getNestedConfig()` method on your nested model. Notice this method should return a `GridFieldConfig` object.
```php
namespace App\Models;
use SilverStripe\Forms\GridField\GridFieldConfig;
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
use SilverStripe\ORM\DataObject;
use Symbiote\GridFieldExtensions\GridFieldNestedForm;
class ChildNode extends DataObject
{
//...
private static $has_one = [
'ParentNode' => ParentNode::class,
];
private static $has_many = [
'GrandChildNodes' => GrandChildNode::class,
];
public function getNestedConfig(): GridFieldConfig
{
$config = new GridFieldConfig_RecordEditor();
$config->addComponent(GridFieldNestedForm::create()->setRelationName('GrandChildNodes'));
return $config;
}
}
```
```php
namespace App\Models;
use SilverStripe\ORM\DataObject;
class GrandChildNode extends DataObject
{
//...
private static $has_one = [
'ChildNode' => ChildNode::class,
];
}
```
```php
namespace App\Admin;
use App\Models\ParentNode;
use SilverStripe\Admin\ModelAdmin;
use SilverStripe\Forms\GridField\GridFieldConfig;
use Symbiote\GridFieldExtensions\GridFieldNestedForm;
class MyAdminSection extends ModelAdmin
{
private static string $url_segment = 'my-admin-section';
private static string $menu_title = 'My Admin Section';
private static array $managed_models = [
ParentNode::class
];
protected function getGridFieldConfig(): GridFieldConfig
{
$config = parent::getGridFieldConfig();
$config->addComponent(GridFieldNestedForm::create()->setRelationName('ChildNodes'));
return $config;
}
}
```
There is also the possibility to use Nested GridField with the data structure ArrayList. To do this, you can use the following approach.
```php
namespace App\Models;
use MyDataObject;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
class MyDataSet extends DataObject
{
//...
public function getMyArrayList() {
$list = ArrayList::create();
$data = MyDataObject::get();
foreach ($data as $value) {
$list->push($value);
}
return $list;
}
}
```
```php
namespace App\Admin;
use App\Models\MyDataSet;
use SilverStripe\Admin\ModelAdmin;
use SilverStripe\Forms\GridField\GridFieldConfig;
use Symbiote\GridFieldExtensions\GridFieldNestedForm;
class MyAdminSection extends ModelAdmin
{
//...
private static array $managed_models = [
MyDataSet::class
];
protected function getGridFieldConfig(): GridFieldConfig
{
$config = parent::getGridFieldConfig();
$config->addComponent(GridFieldNestedForm::create()->setRelationName('getMyArrayList'));
return $config;
}
}
```
Notice that instead of the name of a relation, we're passing a method name into `getMyArrayList()`. That method must return an instance of `SS_List` to be used in the GridField.
#### Additional features and settings
1. You can set the maximum number of nested levels using a `$default_max_nesting_level` configuration property. The default value is 10 levels.
```yml
Symbiote\GridFieldExtensions\GridFieldNestedForm:
default_max_nesting_level: 5
```
You can also set this limit for a specific nested GridField using the `setMaxNestingLevel()` method.
```php
public function getNestedConfig(): GridFieldConfig
{
$config = new GridFieldConfig_RecordEditor();
$config->addComponent(GridFieldNestedForm::create()->setMaxNestingLevel(5));
return $config;
}
```
1. You can also modify the default config (a `GridFieldConfig_RecordEditor`) via an extension to the nested model class, by implementing
`updateNestedConfig`, which will get the config object as the parameter.
```php
namespace App\Extensions;
use SilverStripe\Core\Extension;
class NestedObjectExtension extends Extension
{
public function updateNestedConfig(GridFieldConfig &$config)
{
$config->removeComponentsByType(GridFieldPaginator::class);
}
}
```

View File

@ -0,0 +1,143 @@
Feature: Using Nested GridField with non-hierarchical relational data
As a content editor
I want to see all children of non-hierarchical relational data in nested GridField
Background:
Given there are the following SilverStripe\FrameworkTest\Fields\NestedGridField\LeafNode records
"""
leaf-node-1:
Name: "Leaf Node One"
Category: "A"
leaf-node-2:
Name: "Leaf Node Two"
Category: "D"
leaf-node-3:
Name: "Leaf Node Three"
Category: "C"
leaf-node-4:
Name: "Leaf Node Four"
Category: "B"
"""
Given there are the following SilverStripe\FrameworkTest\Fields\NestedGridField\BranchNode records
"""
branch-node-1:
Name: "Branch Node One"
Category: "D"
LeafNodes:
- =>SilverStripe\FrameworkTest\Fields\NestedGridField\LeafNode.leaf-node-1
- =>SilverStripe\FrameworkTest\Fields\NestedGridField\LeafNode.leaf-node-2
- =>SilverStripe\FrameworkTest\Fields\NestedGridField\LeafNode.leaf-node-3
- =>SilverStripe\FrameworkTest\Fields\NestedGridField\LeafNode.leaf-node-4
branch-node-2:
Name: "Branch Node Two"
Category: "C"
branch-node-3:
Name: "Branch Node Three"
Category: "B"
branch-node-4:
Name: "Branch Node Four"
Category: "A"
"""
And there are the following SilverStripe\FrameworkTest\Fields\NestedGridField\RootNode records
"""
root-node-1:
Name: "Root Node One"
BranchNodes:
- =>SilverStripe\FrameworkTest\Fields\NestedGridField\BranchNode.branch-node-1
- =>SilverStripe\FrameworkTest\Fields\NestedGridField\BranchNode.branch-node-2
root-node-2:
Name: "Root Node Two"
BranchNodes:
- =>SilverStripe\FrameworkTest\Fields\NestedGridField\BranchNode.branch-node-3
- =>SilverStripe\FrameworkTest\Fields\NestedGridField\BranchNode.branch-node-4
"""
And there are the following SilverStripe\FrameworkTest\Fields\NestedGridField\NonRelationalData records
"""
data-1:
Name: "Data Set 1"
"""
And I go to "/dev/build?flush"
And the "group" "EDITOR" has permissions "Access to 'Pages' section" and "Access to 'Nested GridField Section' section" and "Access to 'Security' section"and "TEST_DATAOBJECT_EDIT"
And I am logged in as a member of "EDITOR" group
And I go to "/admin/nested-gridfield-section"
Scenario: I want to see all items in nested GridField
Given I should see "Root Node One" in the ".ss-gridfield-item.first.odd" element
And I should see "Root Node Two" in the ".ss-gridfield-item.last.even" element
When I click on the ".ss-gridfield-item.first.odd button" element
And I should see "Branch Node One" in the ".nested-gridfield.odd" element
And I should see "Branch Node Two" in the ".nested-gridfield.odd" element
When I click on the ".nested-gridfield.odd .ss-gridfield-items button" element
And I should see "Leaf Node One" in the ".nested-gridfield.odd" element
And I should see "Leaf Node Two" in the ".nested-gridfield.odd" element
And I should see "Leaf Node Three" in the ".nested-gridfield.odd" element
And I should see "Leaf Node Four" in the ".nested-gridfield.odd" element
Scenario: I want to edit and delete items in nested GridField
Given I should see "Root Node Two" in the ".ss-gridfield-item.last.even" element
When I click on the ".ss-gridfield-item.last.even button" element
And I should see "Branch Node Three" in the ".nested-gridfield.even .ss-gridfield-item.first.odd" element
And I should see "Branch Node Four" in the ".nested-gridfield.even .ss-gridfield-item.last.even" element
When I click on the ".nested-gridfield.even .ss-gridfield-item.first.odd" element
And I fill in "Name" with "New Branch Node"
And I press the "Save" button
Then I click on the ".toolbar__back-button" element
And I should see "New Branch Node" in the ".nested-gridfield.even .ss-gridfield-item.first.odd" element
And I should see "Branch Node Four" in the ".nested-gridfield.even .ss-gridfield-item.last.even" element
And I click on the ".nested-gridfield.even .ss-gridfield-item.last.even button[aria-label='View actions']" element
And I click on the ".nested-gridfield.even .ss-gridfield-item.last.even button.action--delete" element, confirming the dialog
And I should see "New Branch Node" in the ".nested-gridfield.even .ss-gridfield-item.first.odd" element
And I should not see "Branch Node Four"
Scenario: I can to sort items in nested GridField
Given I should see "Root Node One" in the ".ss-gridfield-item.first.odd" element
And I should see "Root Node Two" in the ".ss-gridfield-item.last.even" element
When I click on the ".ss-gridfield-item.first.odd button" element
And I should see "Branch Node One" in the ".nested-gridfield.odd .ss-gridfield-item.first.odd" element
And I should see "Branch Node Two" in the ".nested-gridfield.odd .ss-gridfield-item.last.even" element
Then I click on the ".nested-gridfield.odd button[value='Category']" element
And I should see "Branch Node Two" in the ".nested-gridfield.odd .ss-gridfield-item.first.odd" element
And I should see "Branch Node One" in the ".nested-gridfield.odd .ss-gridfield-item.last.even" element
When I click on the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(2) button" element
And I should see "Leaf Node One" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item:nth-of-type(1)" element
And I should see "Leaf Node Two" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item:nth-of-type(2)" element
And I should see "Leaf Node Three" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item:nth-of-type(3)" element
And I should see "Leaf Node Four" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item:nth-of-type(4)" element
## Category: Leaf Node 1 -> A, Leaf Node 4 -> B, Leaf Node 3 -> C, Leaf Node 4 -> D
## First request returns ASC order
Then I click on the ".nested-gridfield.odd .nested-gridfield button[value='Category']" element
And I should see "Leaf Node One" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item.first.odd" element
And I should see "Leaf Node Four" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item:nth-of-type(2)" element
And I should see "Leaf Node Three" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item:nth-of-type(3)" element
And I should see "Leaf Node Two" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item.last.even" element
## Second request returns DESC order
And I click on the ".nested-gridfield.odd .nested-gridfield button[value='Category']" element
And I should see "Leaf Node Two" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item.first.odd" element
And I should see "Leaf Node Three" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item:nth-of-type(2)" element
And I should see "Leaf Node Four" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item:nth-of-type(3)" element
And I should see "Leaf Node One" in the ".nested-gridfield.odd .nested-gridfield .ss-gridfield-item.last.even" element
Scenario: I can to filter items in nested GridField
Given I should see "Root Node One" in the ".ss-gridfield-item.first.odd" element
When I click on the ".ss-gridfield-item.first.odd button" element
And I should see "Branch Node One" in the ".nested-gridfield.odd .ss-gridfield-item.first.odd" element
And I should see "Branch Node Two" in the ".nested-gridfield.odd .ss-gridfield-item.last.even" element
Then I click on the ".nested-gridfield.odd button[title='Open search and filter']" element
Then I click on the ".nested-gridfield.odd button[title='Advanced']" element
And I fill in "Search__Name" with "One"
And I press the "Search" button
And I should see "Branch Node One" in the ".nested-gridfield.odd .ss-gridfield-item.first.odd" element
And I should not see "Branch Node Two"
Scenario: I want to see all non-relational data in nested GridField
Given I go to "/admin/nested-gridfield-section/non-relational-data"
And I should see "Data Set 1" in the ".ss-gridfield-item.first.odd" element
When I click on the ".ss-gridfield-item:nth-of-type(1) button" element
Then I should see "Walmart" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(1)" element
And I should see "ExxonMobil" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(2)" element
And I should see "Royal Dutch Shell" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(3)" element
And I should see "BP" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(4)" element
And I should see "Sinopec" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(5)" element
Then I click on the ".nested-gridfield.odd button[value='Name']" element
Then I should see "BP" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(1)" element
And I should see "Walmart" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(5)" element

View File

@ -0,0 +1,61 @@
Feature: Using Nested GridField with hierarchical relational data
As a content editor
I want to see all children of hierarchical relational data in nested GridField
Background:
Given I add an extension "SilverStripe\FrameworkTest\Fields\NestedGridField\SecurityAdminExtension" to the "SilverStripe\Admin\SecurityAdmin" class
And there are the following SilverStripe\Security\Member records
"""
Adam:
Name: "Smith"
Jim:
Name: "Morrison"
Tom:
Name: "Ford"
James:
Name: "Dean"
"""
Given there are the following SilverStripe\Security\Group records
"""
group-1:
Title: "Group One"
Members:
- =>SilverStripe\Security\Member.Adam
- =>SilverStripe\Security\Member.Jim
- =>SilverStripe\Security\Member.Tom
- =>SilverStripe\Security\Member.James
group-2:
Title: "Group Two"
"""
And I go to "/dev/build?flush"
And the "group" "EDITOR" has permissions "Access to 'Pages' section" and "Access to 'Security' section"
And I am logged in as a member of "EDITOR" group
And I go to "/admin/security/groups"
Scenario: I want to see all items in nested GridField
Given I should see "Group One" in the ".ss-gridfield-item:nth-of-type(1)" element
And I should see "Group Two" in the ".ss-gridfield-item:nth-of-type(2)" element
When I click on the ".ss-gridfield-item.first.odd button" element
And I should see "Adam" in the ".nested-gridfield.odd" element
And I should see "Jim" in the ".nested-gridfield.odd" element
And I should see "Tom" in the ".nested-gridfield.odd" element
And I should see "James" in the ".nested-gridfield.odd" element
Scenario: I want to edit and delete items in nested GridField
Given I should see "Group One" in the ".ss-gridfield-item:nth-of-type(1)" element
When I click on the ".ss-gridfield-item:nth-of-type(1) button" element
Then I click on the ".nested-gridfield.odd button[value='First Name']" element
And I should see "Adam" in the ".nested-gridfield.odd .ss-gridfield-item.first.odd" element
And I should see "James" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(2)" element
And I should see "Jim" in the ".nested-gridfield.odd .ss-gridfield-item:nth-of-type(3)" element
And I should see "Tom" in the ".nested-gridfield.odd .ss-gridfield-item.last.even" element
When I click on the ".nested-gridfield.odd .ss-gridfield-item.first.odd" element
And I fill in "Name" with "John"
And I press the "Save" button
Then I click on the ".toolbar__back-button" element
Then I click on the ".nested-gridfield.odd button[value='Surname']" element
And I should see "James" in the ".nested-gridfield.odd .ss-gridfield-item.first.odd" element
And I should see "John" in the ".nested-gridfield.odd .ss-gridfield-item.last.even" element
And I click on the ".nested-gridfield.odd .ss-gridfield-item.last.even button[aria-label='View actions']" element
And I click on the ".nested-gridfield.odd .ss-gridfield-item.last.even button.action--delete" element, confirming the dialog
And I should not see "John"