Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOC Document new tab order and form scaffolder changes #549

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 59 additions & 20 deletions en/02_Developer_Guides/00_Model/11_Scaffolding.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,47 +44,86 @@ class MyDataObject extends DataObject
> [!TIP]
> It is typically considered a good practice to wrap your modifications in a call to [`beforeUpdateCMSFields()`](api:SilverStripe\ORM\DataObject::beforeUpdateCMSFields()) - the `updateCMSFields()` extension hook is already triggered by `parent::getCMSFields()`, so this is how you ensure any new fields are added before extensions update your fieldlist.

To fully customise your form fields, start with an empty FieldList.
To define the form fields yourself without using scaffolding, use the `mainTabOnly` option in [`DataObject.scaffold_cms_fields_settings`](api:SilverStripe\ORM\DataObject->scaffold_cms_fields_settings). See [scaffolding options](#scaffolding-options) for details.

```php
namespace App\Model;

use SilverStripe\Forms\CheckboxSetField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\TextareaField;
use SilverStripe\ORM\DataObject;

class MyDataObject extends DataObject
{
// ...

private static array $scaffold_cms_fields_settings = [
'mainTabOnly' => true,
];

public function getCMSFields()
{
$fields = FieldList::create(
TabSet::create(
'Root',
Tab::create(
'Main',
CheckboxSetField::create('IsActive', 'Is active?'),
TextField::create('Title'),
TextareaField::create('Content')
->setRows(5)
)
)
);

$this->extend('updateCMSFields', $fields);

return $fields;
$this->beforeUpdateCMSFields(function (FieldList $fields) {
$fields->addFieldsToTab('Root.Main', [
CheckboxSetField::create('IsActive', 'Is active?'),
TextField::create('Title'),
TextareaField::create('Content')->setRows(5),
]);
});

return parent::getCMSFields();
}
}
```

> [!TIP]
> It is good practice to invoke the `updateCMSFields()` extension hook afterward, so that extensions in modules can apply their functionality to your field list.
Comment on lines -80 to -81
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer relevant since we're calling the parent method.


You can also alter the fields of built-in and module `DataObject` classes by implementing `updateCMSFields()` in [your own Extension](/developer_guides/extending/extensions).

> [!NOTE]
> `FormField` scaffolding takes [`$field_labels` config](#field-labels) into account as well.

## Scaffolding options

`FormScaffolder` has several options that modify the way it scaffolds form fields.

|option|description|
|---|---|
|`tabbed`|Use tabs for the scaffolded fields. All database fields and `has_one` fields will be in a "Root.Main" tab. Fields representing `has_many` and `many_many` relations will either be in "Root.Main" or in "Root.`<relationname>`" tabs.|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fields representing has_many and many_many relations will either be in "Root.Main" or in "Root.<relationname>" tabs.

Shouldn't these always be in the "Root.<relationname>" tab? Seems like that's the point of the option?

Copy link
Member Author

@GuySartorelli GuySartorelli Aug 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tab they're in is determined by the scaffoldFormFieldForHasMany() and scaffoldFormFieldForManyMany() methods which can opt to not include the fields in their own tabs.

This option allows tabs to be used, but it doesn't force it.

|`mainTabOnly`|Only set up the "Root.Main" tab, but skip scaffolding actual form fields or relation tabs. If `tabbed` is false, the `FieldList` will be empty.|
|`restrictFields`|Allow list of field names. If populated, any database fields and fields representing `has_one` relations not in this array won't be scaffolded.|
|`ignoreFields`|Deny list of field names. If populated, database fields and fields representing `has_one` relations which *are* in this array won't be scaffolded.|
|`fieldClasses`|Optional mapping of field names to subclasses of `FormField`.|
|`includeRelations`|Whether to include `has_many` and `many_many` relations.|
|`restrictRelations`|Allow list of field names. If populated, form fields representing `has_many` and `many_many` relations not in this array won't be scaffolded.|
|`ignoreRelations`|Deny list of field names. If populated, form fields representing `has_many` and `many_many` relations which *are* in this array won't be scaffolded.|

You can set these options for the scaffolding of the fields in your model's `getCMSFields()` field list by setting the [`DataObject.scaffold_cms_fields_settings`](api:SilverStripe\ORM\DataObject->scaffold_cms_fields_settings) configuration property.

```php
namespace App\Model;

use SilverStripe\Forms\HiddenField;
use SilverStripe\ORM\DataObject;

class MyDataObject extends DataObject
{
// ...

private static array $scaffold_cms_fields_settings = [
'includeRelations' => false,
'ignoreFields' => [
'MyDataOnlyField',
],
'fieldClasses' => [
'MyHiddenField' => HiddenField::class,
],
];
}
```

You can also set this configuration in [extensions](/developer_guides/extending/extensions), for example if your extension is adding new database fields that you don't want to be edited via form fields in the CMS.

## Scaffolding for relations

Form fields are also automatically scaffolded for `has_one`, `has_many`, and `many_many` relations. These have sensible default implementations, and you can also customise what form field will be used for any given `DataObject` model.
Expand Down
11 changes: 11 additions & 0 deletions en/02_Developer_Guides/03_Forms/06_Tabbed_Forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ $fields->addFieldsToTab('Root.Content', [
]);
```

## Change the order of tabs

If you need to change the order of tabs, for example if the tabs were scaffolded through [`FormScaffolder`](api:SilverStripe\Forms\FormScaffolder),
you can do so by passing the correct order of the tabs into [`TabSet::changeTabOrder()`](api:SilverStripe\Forms\TabSet::changeTabOrder()).

If there are more tabs in the tab set than you include in the tab order, they will be added after the tabs you explicitly included.

```php
$fields->fieldByName('Root')->changeTabOrder(['FirstTab', 'SecondTab']);
```

## API documentation

- [FormScaffolder](api:SilverStripe\Forms\FormScaffolder)
28 changes: 25 additions & 3 deletions en/08_Changelogs/5.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ title: 5.3.0 (unreleased)
- [High-level API for converting files](#file-converter)
- [Improve customisability of rendered images](#image-rendering)
- [Validation for inline-editable elemental blocks](#elemental-validation)
- [Define scaffolded form fields for relations to `DataObject` models](#scaffolded-relation-formfields)
- [`FormField` scaffolding for `DataObject` models](#scaffolding)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the existing form field scaffolding item as a sub-item in a new section, so now there's an overall formfield scaffolding section with both the existing docs and the new docs inside it.

- [Support for `JOIN` in SQL `UPDATE`](#sql-update-join)
- [Autologin token regeneration changes](#autologin-token-regeneration)
- [Other new features](#other-new-features)
Expand Down Expand Up @@ -82,9 +82,13 @@ Validation can be added to a content block using standard [Model Validation and

Elemental data is no longer sent when saving the parent `DataObject` (usually a `Page`) that contains an [`ElementalAreaField`](api:DNADesign\Elemental\Forms\ElementalAreaField). Instead, when saving the parent `DataObject`, all the child inline-editable elements that have unsaved changes are inline saved at the same time. This change was done to consolidate the saving process down to a single code path. The code that was previously used to process any element data sent with the parent data has been removed.

### Define scaffolded form fields for relations to `DataObject` models {#scaffolded-relation-formfields}
### `FormField` scaffolding for `DataObject` models {#scaffolding}

Most `DataObject` classes will rely on some amount of automatic scaffolding of form fields in their [`getCMSFields()`](api:SilverStripe\ORM\DataObject::getCMSFields()) implementations. However, it's common for modules to provide a specialised form field which is intended to always be used with a specific `DataObject` class. In those cases, even though you always want to use that form field with that class, you have to copy some boilerplate code from the module's documentation to set it up.
We've made a few improvements to how form fields are scaffolded for [`DataObject::getCMSFields()`](api:SilverStripe\ORM\DataObject::getCMSFields()).

#### Define scaffolded form fields for relations {#scaffolding-relation-formfields}

Most `DataObject` classes will rely on some amount of automatic scaffolding of form fields in their `getCMSFields()` implementations. However, it's common for modules to provide a specialised form field which is intended to always be used with a specific `DataObject` class. In those cases, even though you always want to use that form field with that class, you have to copy some boilerplate code from the module's documentation to set it up.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only change to this line (see removal of the same line above) was removing the API link since we're linking to that in the parent heading section instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth mentioning that SiteTree subclasses would be auto-scaffolded, I know it's documented elsewhere though it's such a common use case I think it's worth mentioning here so people don't get confused why things aren't not working

Copy link
Member Author

@GuySartorelli GuySartorelli Aug 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is for CMS 5, where SiteTree formfields are never scaffolded.
SiteTree scaffolding is happening in CMS 6.


You can now define what form fields should be used when scaffolding form fields for `has_one`, `has_many`, and `many_many` relations. This is defined on the class on the child-side of the relationship. For example, for the below `has_one` relation you would implement [`scaffoldFormFieldForHasOne()`](api:SilverStripe\ORM\DataObject::scaffoldFormFieldForHasOne()) on the `MyChild` class.

Expand All @@ -106,6 +110,23 @@ This means modules can pre-define the form field that should be used for their c

For more information see [scaffolding for relations](/developer_guides/model/scaffolding/#scaffolding-for-relations).

#### `FormScaffolder` options {#scaffolding-options}

The [`FormScaffolder`](api:SilverStripe\Forms\FormScaffolder) class (which is responsible for scaffolding form fields for `getCMSFields()`) has some new options:

|option|description|
|---|---|
|`mainTabOnly`|Only set up the "Root.Main" tab, but skip scaffolding actual form fields or relation tabs. If `tabbed` is false, the `FieldList` will be empty.|
|`ignoreFields`|Deny list of field names. If populated, database fields and fields representing `has_one` relations which *are* in this array won't be scaffolded.|
|`ignoreRelations`|Deny list of field names. If populated, form fields representing `has_many` and `many_many` relations which *are* in this array won't be scaffolded.|
|`restrictRelations`|Allow list of field names. If populated, form fields representing `has_many` and `many_many` relations not in this array won't be scaffolded.|

In particular, the `ignoreFields` and `ignoreRelations` options are useful ways to prevent form fields from being scaffolded at all. You can use this instead of calling `$fields->removeFieldByName()`, for example if you are adding database fields that you don't want to be edited with form fields in the CMS.

These options are now also configurable using the new [`DataObject.scaffold_cms_fields_settings`](api:SilverStripe\ORM\DataObject->scaffold_cms_fields_settings) configuration property.

See the [scaffolding](/developer_guides/model/scaffolding/#scaffolding-options) section for more details.

### Support for `JOIN` in SQL `UPDATE` {#sql-update-join}

The [`SQLUpdate`](api:SilverStripe\ORM\Queries\SQLUpdate) class now supports all of the same `JOIN` operations (using the same methods) that [`SQLSelect`](api:SilverStripe\ORM\Queries\SQLSelect) does.
Expand Down Expand Up @@ -133,6 +154,7 @@ From 6.0 onwards, tokens will never be regenerated during session renewal, and t
- `silverstripe/graphql-devtools` contains a new `GraphQLSchemaInitTask` to help you initialise a basic GraphQL schema.
- [`GridFieldDetailForm_ItemRequest`](api:SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest) now uses a [`PjaxResponseNegotiator`](api:SilverStripe\Control\PjaxResponseNegotiator) to handle PJAX responses for the save and publish actions. This aligns it with responses from other form submissions in the CMS.
- Primitive types inside an iterable object will now be automatically converted to relevant [`DBField`](api:SilverStripe\ORM\FieldType\DBField) types so can be rendered in a template. This allows the use of `return ArrayList::create(['lorem', 123]);` from a [`ContentController`](api:SilverStripe\CMS\Controllers\ContentController) method that is then looped over in a template.
- A new [`TabSet::changeTabOrder()`](api:SilverStripe\Forms\TabSet::changeTabOrder()) method has been added that allows you to change the order of tabs in a `TabSet`. See [tabbed forms](/developer_guides/forms/tabbed_forms/#change-the-order-of-tabs) for details.

## API changes

Expand Down
Loading