diff --git a/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php b/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php index 1bc42369bcc..af245ad261f 100644 --- a/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php +++ b/src/Forms/GridField/GridFieldDetailForm_ItemRequest.php @@ -2,6 +2,7 @@ namespace SilverStripe\Forms\GridField; +use LogicException; use SilverStripe\Admin\LeftAndMain; use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPRequest; @@ -328,7 +329,7 @@ protected function getRightGroupField() /** @var GridFieldDetailForm $component */ $component = $this->gridField->getConfig()->getComponentByType(GridFieldDetailForm::class); $paginator = $this->getGridField()->getConfig()->getComponentByType(GridFieldPaginator::class); - $gridState = $this->getStateManager()->getStateFromRequest($this->gridField, $this->getRequest()); + $gridState = $this->getGridField()->getState(); if ($component && $paginator && $component->getShowPagination()) { $previousIsDisabled = !$this->getPreviousRecordID(); $nextIsDisabled = !$this->getNextRecordID(); @@ -337,8 +338,8 @@ protected function getRightGroupField() LiteralField::create( 'previous-record', HTML::createTag($previousIsDisabled ? 'span' : 'a', [ - 'href' => $previousIsDisabled ? '#' : $this->getEditLink($this->getPreviousRecordID()), - 'data-grid-state' => $gridState, + 'href' => $previousIsDisabled ? '#' : $this->getEditLinkForAdjacentRecord(-1), + 'data-grid-state' => $previousIsDisabled ? $gridState : $this->getGridStateForAdjacentRecord(-1), 'title' => _t(__CLASS__ . '.PREVIOUS', 'Go to previous record'), 'aria-label' => _t(__CLASS__ . '.PREVIOUS', 'Go to previous record'), 'class' => 'btn btn-secondary font-icon-left-open action--previous discard-confirmation' @@ -351,8 +352,8 @@ protected function getRightGroupField() LiteralField::create( 'next-record', HTML::createTag($nextIsDisabled ? 'span' : 'a', [ - 'href' => $nextIsDisabled ? '#' : $this->getEditLink($this->getNextRecordID()), - 'data-grid-state' => $gridState, + 'href' => $nextIsDisabled ? '#' : $this->getEditLinkForAdjacentRecord(+1), + 'data-grid-state' => $nextIsDisabled ? $gridState : $this->getGridStateForAdjacentRecord(+1), 'title' => _t(__CLASS__ . '.NEXT', 'Go to next record'), 'aria-label' => _t(__CLASS__ . '.NEXT', 'Go to next record'), 'class' => 'btn btn-secondary font-icon-right-open action--next discard-confirmation' @@ -413,8 +414,7 @@ protected function getFormActions() ->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action--delete')); } - $gridState = $manager->getStateFromRequest($this->gridField, $this->getRequest()); - $this->gridField->getState(false)->setValue($gridState); + $gridState = $this->gridField->getState(false); $actions->push(HiddenField::create($manager->getStateKey($this->gridField), null, $gridState)); $actions->push($this->getRightGroupField()); @@ -561,37 +561,115 @@ public function getEditLink($id) $id ); - return $this->getStateManager()->addStateToURL($this->gridField, $link); + return $this->gridField->addAllStateToUrl($link); } /** - * @param int $offset The offset from the current record - * @return int|bool + * Return array of GridField items on current page plus + * first item on the next page and last item on the previous page */ - private function getAdjacentRecordID($offset) + private function getGridFieldItemAdjacencies(): array { - $gridField = $this->getGridField(); - $list = $gridField->getManipulatedList(); - $state = $gridField->getState(false); - $gridStateStr = $this->getStateManager()->getStateFromRequest($this->gridField, $this->getRequest()); - if (!empty($gridStateStr)) { - $state->setValue($gridStateStr); - } - $data = $state->getData(); - $paginator = $data->getData('GridFieldPaginator'); + $list = $this->getGridField()->getManipulatedList(); + $paginator = $this->getGridFieldPaginatorState(); if (!$paginator) { - return false; + return []; } - $currentPage = $paginator->getData('currentPage'); $itemsPerPage = $paginator->getData('itemsPerPage'); $limit = $itemsPerPage + 2; $limitOffset = max(0, $itemsPerPage * ($currentPage-1) -1); - $map = $list->limit($limit, $limitOffset)->column('ID'); + return $list->limit($limit, $limitOffset)->column('ID'); + } + + /** + * Get the current paginator state + */ + private function getGridFieldPaginatorState(): GridState_Data + { + $state = $this->getGridField()->getState(false); + $gridStateStr = $this->getStateManager()->getStateFromRequest($this->gridField, $this->getRequest()); + if (!empty($gridStateStr)) { + $state->setValue($gridStateStr); + } + + return $state->getData()->getData('GridFieldPaginator'); + } + + /** + * Get the grid state for an adjacent record + */ + private function getGridStateForAdjacentRecord(int $offset): GridState_Data + { + $gridField = $this->getGridField(); + $map = $this->getGridFieldItemAdjacencies(); + if (empty($map)) { + throw new LogicException('No adjacent records exist'); + } + + $state = clone $gridField->getState(); + $index = array_search($this->record->ID, $map); + $position = $index + $offset; + + $currentPage = $this->getGridFieldPaginatorState()->getData('currentPage'); + $itemsPerPage = $this->getGridFieldPaginatorState()->getData('itemsPerPage'); + $page = $currentPage; + $hasMorePages = $this->getNumPages($gridField) > $currentPage; + + if ($position === 0 && $currentPage > 1) { + $page = $currentPage - 1; + } elseif ($hasMorePages && $position >= $itemsPerPage + 1) { + $page = $currentPage + 1; + } + $state->GridFieldPaginator->currentPage = (int)$page; + + return $state; + } + + /** + * Get the edit link for an adjacent record + */ + private function getEditLinkForAdjacentRecord(int $offset): string + { + $link = Controller::join_links( + $this->gridField->Link(), + 'item', + $this->getAdjacentRecordID($offset) + ); + $state = $this->getGridStateForAdjacentRecord($offset); + // Get a dummy gridfield so we can set some future state without affecting the current gridfield + $gridField = clone $this->gridField; + $gridField->getState(false)->setValue($state); + return $gridField->addAllStateToUrl($link); + } + + /** + * @param int $offset The offset from the current record + * @return int|bool + */ + private function getAdjacentRecordID($offset) + { + $map = $this->getGridFieldItemAdjacencies(); + if (empty($map)) { + return false; + } $index = array_search($this->record->ID, $map ?? []); - return isset($map[$index+$offset]) ? $map[$index+$offset] : false; + $position = $index + $offset; + return isset($map[$position]) ? $map[$position] : false; + } + + /** + * Gets the number of GridField pages + */ + private function getNumPages(GridField $gridField): int + { + return $gridField + ->getConfig() + ->getComponentByType(GridFieldPaginator::class) + ->getTemplateParameters($gridField) + ->toMap()['NumPages']; } /** diff --git a/src/Forms/GridField/GridState_Data.php b/src/Forms/GridField/GridState_Data.php index 763fff28e67..2bbc7d791f4 100644 --- a/src/Forms/GridField/GridState_Data.php +++ b/src/Forms/GridField/GridState_Data.php @@ -40,6 +40,11 @@ public function __call($name, $arguments) return $this->getData($name, $default); } + public function __clone() + { + $this->data = $this->toArray(); + } + /** * Initialise the defaults values for the grid field state * These values won't be included in getChangesArray()