From cc712892a95b74c8bcdea5f2c1469286e298203e Mon Sep 17 00:00:00 2001 From: Andre Kiste Date: Mon, 19 Nov 2018 11:06:47 +1300 Subject: [PATCH] NEW Port betterbuttons to framework (#8569) * MINOR: Add `Previous`, `Next` and `Create New` actions in edit form If the form is opened via a grid field, the filters will be retained so the previous/next record opened will be correct * MINOR: Add ability to customise the visibility of the `Previous`, `Next` and `Add` buttons at a `GridField` level * Fix invalid action when pressing the `New` button in an edit form unless `betterbuttons` module was installed * - Merge `showPrevious` and `showNext` to `showPagination` for grid fields - Update documentation - Improve performance for next/previous buttons by not fetching all list records - Refactoring * Refactor to fail gracefully on GridFieldPaginator --- _config/buttons.yml | 7 + .../How_Tos/Customise_CMS_Menu.md | 21 +++ .../GridFieldConfig_RecordEditor.php | 6 +- src/Forms/GridField/GridFieldDetailForm.php | 91 ++++++++++- .../GridFieldDetailForm_ItemRequest.php | 151 ++++++++++++++++++ src/Forms/GridField/GridFieldEditButton.php | 7 +- src/Forms/GridField/GridFieldPaginator.php | 1 + 7 files changed, 275 insertions(+), 9 deletions(-) create mode 100644 _config/buttons.yml diff --git a/_config/buttons.yml b/_config/buttons.yml new file mode 100644 index 000000000..df80742db --- /dev/null +++ b/_config/buttons.yml @@ -0,0 +1,7 @@ +--- +Name: buttons +--- +SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest: + formActions: + showPagination: true + showAdd: true diff --git a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Customise_CMS_Menu.md b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Customise_CMS_Menu.md index 9b36c52c4..848f07033 100644 --- a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Customise_CMS_Menu.md +++ b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Customise_CMS_Menu.md @@ -124,6 +124,27 @@ SilverStripe\Admin\LeftAndMain: 'Feedback': '' ``` +## Customising the CMS form actions + +The `Previous`, `Next` and `Add` actions on the edit form are visible by default but can be hidden globally by adding the following `.yml` config: + +```yml +SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest: + formActions: + showPagination: false + showAdd: false +``` + +You can also override this for a specific `GridField` instance when using the `GridFieldConfig_RecordEditor` constructor: + +```php +$grid = new GridField( + "pages", + "All Pages", + SiteTree::get(), + GridFieldConfig_RecordEditor::create(null, false, false)); +``` + ## Related * [How to extend the CMS interface](extend_cms_interface) diff --git a/src/Forms/GridField/GridFieldConfig_RecordEditor.php b/src/Forms/GridField/GridFieldConfig_RecordEditor.php index 5a831e106..28d7b6749 100644 --- a/src/Forms/GridField/GridFieldConfig_RecordEditor.php +++ b/src/Forms/GridField/GridFieldConfig_RecordEditor.php @@ -10,8 +10,10 @@ class GridFieldConfig_RecordEditor extends GridFieldConfig /** * * @param int $itemsPerPage - How many items per page should show up + * @param bool $showPagination Whether the `Previous` and `Next` buttons should display or not, leave as null to use default + * @param bool $showAdd Whether the `Add` button should display or not, leave as null to use default */ - public function __construct($itemsPerPage = null) + public function __construct($itemsPerPage = null, $showPagination = null, $showAdd = null) { parent::__construct(); @@ -26,7 +28,7 @@ class GridFieldConfig_RecordEditor extends GridFieldConfig $this->addComponent(new GridField_ActionMenu()); $this->addComponent(new GridFieldPageCount('toolbar-header-right')); $this->addComponent($pagination = new GridFieldPaginator($itemsPerPage)); - $this->addComponent(new GridFieldDetailForm()); + $this->addComponent(new GridFieldDetailForm(null, $showPagination, $showAdd)); $sort->setThrowExceptionOnBadDataType(false); $filter->setThrowExceptionOnBadDataType(false); diff --git a/src/Forms/GridField/GridFieldDetailForm.php b/src/Forms/GridField/GridFieldDetailForm.php index e32d42370..215ff04c1 100644 --- a/src/Forms/GridField/GridFieldDetailForm.php +++ b/src/Forms/GridField/GridFieldDetailForm.php @@ -38,11 +38,20 @@ class GridFieldDetailForm implements GridField_URLHandler protected $template = null; /** - * * @var string */ protected $name; + /** + * @var bool + */ + protected $showPagination; + + /** + * @var bool + */ + protected $showAdd; + /** * @var Validator The form validator used for both add and edit fields. */ @@ -79,10 +88,14 @@ class GridFieldDetailForm implements GridField_URLHandler * controller who wants to display the getCMSFields * * @param string $name The name of the edit form to place into the pop-up form + * @param bool $showPagination Whether the `Previous` and `Next` buttons should display or not, leave as null to use default + * @param bool $showAdd Whether the `Add` button should display or not, leave as null to use default */ - public function __construct($name = 'DetailForm') + public function __construct($name = null, $showPagination = null, $showAdd = null) { - $this->name = $name; + $this->setName($name ?: 'DetailForm'); + $this->setShowPagination($showPagination); + $this->setShowAdd($showAdd); } /** @@ -93,6 +106,10 @@ class GridFieldDetailForm implements GridField_URLHandler */ public function handleItem($gridField, $request) { + if ($gridStateStr = $request->getVar('gridState')) { + $gridField->getState(false)->setValue($gridStateStr); + } + // Our getController could either give us a true Controller, if this is the top-level GridField. // It could also give us a RequestHandler in the form of GridFieldDetailForm_ItemRequest if this is a // nested GridField. @@ -102,7 +119,7 @@ class GridFieldDetailForm implements GridField_URLHandler if (is_numeric($request->param('ID'))) { /** @var Filterable $dataList */ $dataList = $gridField->getList(); - $record = $dataList->byID($request->param("ID")); + $record = $dataList->byID($request->param('ID')); } else { $record = Injector::inst()->create($gridField->getModelClass()); } @@ -179,6 +196,68 @@ class GridFieldDetailForm implements GridField_URLHandler return $this->name; } + /** + * @return bool + */ + protected function getDefaultShowPagination() + { + $formActionsConfig = GridFieldDetailForm_ItemRequest::config()->get('formActions'); + return isset($formActionsConfig['showPagination']) ? (boolean) $formActionsConfig['showPagination'] : false; + } + + /** + * @return bool + */ + public function getShowPagination() + { + if ($this->showPagination === null) { + return $this->getDefaultShowPagination(); + } + + return (boolean) $this->showPagination; + } + + /** + * @param bool|null $showPagination + * @return GridFieldDetailForm + */ + public function setShowPagination($showPagination) + { + $this->showPagination = $showPagination; + return $this; + } + + /** + * @return bool + */ + protected function getDefaultShowAdd() + { + $formActionsConfig = GridFieldDetailForm_ItemRequest::config()->get('formActions'); + return isset($formActionsConfig['showAdd']) ? (boolean) $formActionsConfig['showAdd'] : false; + } + + /** + * @return bool + */ + public function getShowAdd() + { + if ($this->showAdd === null) { + return $this->getDefaultShowAdd(); + } + + return (boolean) $this->showAdd; + } + + /** + * @param bool|null $showAdd + * @return GridFieldDetailForm + */ + public function setShowAdd($showAdd) + { + $this->showAdd = $showAdd; + return $this; + } + /** * @param Validator $validator * @return $this @@ -232,8 +311,8 @@ class GridFieldDetailForm implements GridField_URLHandler { if ($this->itemRequestClass) { return $this->itemRequestClass; - } elseif (ClassInfo::exists(static::class . "_ItemRequest")) { - return static::class . "_ItemRequest"; + } elseif (ClassInfo::exists(static::class . '_ItemRequest')) { + return static::class . '_ItemRequest'; } else { return GridFieldDetailForm_ItemRequest::class; } diff --git a/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php b/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php index 5667ee371..2d6cae3d1 100644 --- a/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php +++ b/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php @@ -7,9 +7,12 @@ use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\RequestHandler; +use SilverStripe\Core\Config\Config; +use SilverStripe\Forms\CompositeField; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\FormAction; +use SilverStripe\Forms\HiddenField; use SilverStripe\Forms\LiteralField; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataObject; @@ -278,6 +281,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler $canEdit = $this->record->canEdit(); $canDelete = $this->record->canDelete(); $actions = new FieldList(); + if ($this->record->ID !== 0) { if ($canEdit) { $actions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Save', 'Save')) @@ -290,6 +294,45 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler ->setUseButtonTag(true) ->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action--delete')); } + + $gridStateStr = $this->getRequest()->requestVar('gridState'); + + $this->gridField->getState(false)->setValue($gridStateStr); + $actions->push(HiddenField::create('gridState', null, $gridStateStr)); + + $rightGroup = CompositeField::create()->setName('RightGroup'); + $rightGroup->addExtraClass('right'); + $rightGroup->setFieldHolderTemplate(get_class($rightGroup) . '_holder_buttongroup'); + + $previousAndNextGroup = CompositeField::create()->setName('PreviousAndNextGroup'); + $previousAndNextGroup->addExtraClass('rounded'); + $previousAndNextGroup->setFieldHolderTemplate(get_class($previousAndNextGroup) . '_holder_buttongroup'); + + + $component = $this->gridField->getConfig()->getComponentByType(GridFieldDetailForm::class); + + if ($component->getShowPagination()) { + $previousAndNextGroup->push(FormAction::create('doPrevious') + ->setUseButtonTag(true) + ->setAttribute('data-grid-state', $gridStateStr) + ->setDisabled(!$this->getPreviousRecordID()) + ->addExtraClass('btn btn-secondary font-icon-left-open action--previous discard-confirmation')); + + $previousAndNextGroup->push(FormAction::create('doNext') + ->setUseButtonTag(true) + ->setAttribute('data-grid-state', $gridStateStr) + ->setDisabled(!$this->getNextRecordID()) + ->addExtraClass('btn btn-secondary font-icon-right-open action--next discard-confirmation')); + } + + $rightGroup->push($previousAndNextGroup); + + if ($component->getShowAdd()) { + $rightGroup->push(FormAction::create('doNew') + ->setUseButtonTag(true) + ->setAttribute('data-grid-state', $this->getRequest()->getVar('gridState')) + ->addExtraClass('btn btn-primary font-icon-plus rounded action--new discard-confirmation')); + } } else { // adding new record //Change the Save label to 'Create' $actions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Create', 'Create')) @@ -309,7 +352,13 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler $actions->push(new LiteralField('cancelbutton', $text)); } } + $this->extend('updateFormActions', $actions); + + if (isset($rightGroup)) { + $actions->push($rightGroup); + } + return $actions; } @@ -410,6 +459,108 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler return $this->redirectAfterSave($isNewRecord); } + /** + * Goes to the previous record + * @param array $data The form data + * @param Form $form The Form object + * @return HTTPResponse + */ + public function doPrevious($data, $form) + { + $this->getToplevelController()->getResponse()->addHeader('X-Pjax', 'Content'); + $link = $this->getEditLink($this->getPreviousRecordID()); + return $this->redirect($link); + } + + /** + * Goes to the next record + * @param array $data The form data + * @param Form $form The Form object + * @return HTTPResponse + */ + public function doNext($data, $form) + { + $this->getToplevelController()->getResponse()->addHeader('X-Pjax', 'Content'); + $link = $this->getEditLink($this->getNextRecordID()); + return $this->redirect($link); + } + + /** + * Creates a new record. If you're already creating a new record, + * this forces the URL to change. + * + * @param array $data The form data + * @param Form $form The Form object + * @return HTTPResponse + */ + public function doNew($data, $form) + { + return $this->redirect(Controller::join_links($this->gridField->Link('item'), 'new')); + } + + /** + * Gets the edit link for a record + * + * @param int $id The ID of the record in the GridField + * @return string + */ + public function getEditLink($id) + { + return Controller::join_links( + $this->gridField->Link(), + 'item', + $id, + '?gridState=' . urlencode($this->gridField->getState(false)->Value()) + ); + } + + /** + * @param int $offset The offset from the current record + * @return int|bool + */ + private function getAdjacentRecordID($offset) + { + $gridField = $this->getGridField(); + $gridStateStr = $this->getRequest()->requestVar('gridState'); + $state = $gridField->getState(false); + $state->setValue($gridStateStr); + $data = $state->getData(); + $paginator = $data->getData('GridFieldPaginator'); + if (!$paginator) { + return false; + } + + $currentPage = $data->getData('GridFieldPaginator')->getData('currentPage'); + $itemsPerPage = $data->getData('GridFieldPaginator')->getData('itemsPerPage'); + + $limit = $itemsPerPage + 2; + $limitOffset = max(0, $itemsPerPage * ($currentPage-1) -1); + + $map = $gridField->getManipulatedList()->limit($limit, $limitOffset)->column('ID'); + $index = array_search($this->record->ID, $map); + return isset($map[$index+$offset]) ? $map[$index+$offset] : false; + } + + /** + * Gets the ID of the previous record in the list. + * + * @return int + */ + public function getPreviousRecordID() + { + return $this->getAdjacentRecordID(-1); + } + + /** + * Gets the ID of the next record in the list. + * + * @return int + */ + public function getNextRecordID() + { + return $this->getAdjacentRecordID(1); + } + /** * Response object for this request after a successful save * diff --git a/src/Forms/GridField/GridFieldEditButton.php b/src/Forms/GridField/GridFieldEditButton.php index e56ed330f..b12a47bca 100644 --- a/src/Forms/GridField/GridFieldEditButton.php +++ b/src/Forms/GridField/GridFieldEditButton.php @@ -62,7 +62,12 @@ class GridFieldEditButton implements GridField_ColumnProvider, GridField_ActionP */ public function getUrl($gridField, $record, $columnName) { - return Controller::join_links($gridField->Link('item'), $record->ID, 'edit'); + return Controller::join_links( + $gridField->Link('item'), + $record->ID, + 'edit', + '?gridState=' . urlencode($gridField->getState(false)->Value()) + ); } /** diff --git a/src/Forms/GridField/GridFieldPaginator.php b/src/Forms/GridField/GridFieldPaginator.php index f9aa43add..4fd7662a6 100755 --- a/src/Forms/GridField/GridFieldPaginator.php +++ b/src/Forms/GridField/GridFieldPaginator.php @@ -144,6 +144,7 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu // Force the state to the initial page if none is set $state->currentPage(1); + $state->itemsPerPage($this->getItemsPerPage()); return $state; }