From ef354511d76ffb9a632ce6ea313bf0bb5f7366e6 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Fri, 28 Jul 2017 16:09:48 +0100 Subject: [PATCH 01/12] FIX Forms now instantiate fields with correct record context on save --- code/GridFieldAddNewInlineButton.php | 2 +- code/GridFieldEditableColumns.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/GridFieldAddNewInlineButton.php b/code/GridFieldAddNewInlineButton.php index 3b776dd..47b6f17 100755 --- a/code/GridFieldAddNewInlineButton.php +++ b/code/GridFieldAddNewInlineButton.php @@ -147,7 +147,6 @@ class GridFieldAddNewInlineButton implements GridField_HTMLProvider, GridField_S $editable = $grid->getConfig()->getComponentByType('GridFieldEditableColumns'); /** @var GridFieldOrderableRows $sortable */ $sortable = $grid->getConfig()->getComponentByType('GridFieldOrderableRows'); - $form = $editable->getForm($grid, $record); if(!singleton($class)->canCreate()) { return; @@ -157,6 +156,7 @@ class GridFieldAddNewInlineButton implements GridField_HTMLProvider, GridField_S $item = $class::create(); $extra = array(); + $form = $editable->getForm($grid, $item); $form->loadDataFrom($fields, Form::MERGE_CLEAR_MISSING); $form->saveInto($item); diff --git a/code/GridFieldEditableColumns.php b/code/GridFieldEditableColumns.php index b69d4e6..996dbf9 100644 --- a/code/GridFieldEditableColumns.php +++ b/code/GridFieldEditableColumns.php @@ -94,8 +94,6 @@ class GridFieldEditableColumns extends GridFieldDataColumns implements /** @var GridFieldOrderableRows $sortable */ $sortable = $grid->getConfig()->getComponentByType('GridFieldOrderableRows'); - $form = $this->getForm($grid, $record); - foreach($value[__CLASS__] as $id => $fields) { if(!is_numeric($id) || !is_array($fields)) { continue; @@ -107,6 +105,8 @@ class GridFieldEditableColumns extends GridFieldDataColumns implements continue; } + $form = $this->getForm($grid, $item); + $extra = array(); $form->loadDataFrom($fields, Form::MERGE_CLEAR_MISSING); From e95c92a16829eac0d73a7ab4e431bf5fdf236e57 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Thu, 20 Jul 2017 11:51:24 +1200 Subject: [PATCH 02/12] NEW Add paginator with configurable page sizes --- code/GridFieldConfigurablePaginator.php | 442 +++++++++++++++++++ css/GridFieldExtensions.css | 390 ++++++++-------- javascript/GridFieldExtensions.js | 9 + templates/GridFieldConfigurablePaginator.ss | 30 ++ tests/GridFieldConfigurablePaginatorTest.php | 217 +++++++++ 5 files changed, 904 insertions(+), 184 deletions(-) create mode 100644 code/GridFieldConfigurablePaginator.php create mode 100644 templates/GridFieldConfigurablePaginator.ss create mode 100644 tests/GridFieldConfigurablePaginatorTest.php diff --git a/code/GridFieldConfigurablePaginator.php b/code/GridFieldConfigurablePaginator.php new file mode 100644 index 0000000..6c785f9 --- /dev/null +++ b/code/GridFieldConfigurablePaginator.php @@ -0,0 +1,442 @@ +setPageSizes($pageSizes ?: Config::inst()->get('GridFieldConfigurablePaginator', 'default_page_sizes')); + + if (!$itemsPerPage) { + $itemsPerPage = $this->pageSizes[0]; + } + + parent::__construct($itemsPerPage); + } + + /** + * Get the total number of records in the list + * + * @return int + */ + public function getTotalRecords() + { + return (int) $this->getGridField()->getList()->count(); + } + + /** + * Get the first shown record number + * + * @return int + */ + public function getFirstShown() + { + $firstShown = $this->getGridPagerState()->firstShown ?: 1; + // Prevent visiting a page with an offset higher than the total number of items + if ($firstShown > $this->getTotalRecords()) { + $this->getGridPagerState()->firstShown = $firstShown = 1; + } + return $firstShown; + } + + /** + * Set the first shown record number. Will be stored in the state. + * + * @param int $firstShown + * @return $this + */ + public function setFirstShown($firstShown = 1) + { + $this->getGridPagerState()->firstShown = (int) $firstShown; + return $this; + } + + /** + * Get the last shown record number + * + * @return int + */ + public function getLastShown() + { + return min($this->getTotalRecords(), $this->getFirstShown() + $this->getItemsPerPage() - 1); + } + + /** + * Get the total number of pages, given the current number of items per page. The total + * pages might be higher than / if the first shown record + * is half way through a standard page break point. + * + * @return int + */ + public function getTotalPages() + { + // Pages before + $pages = ceil(($this->getFirstShown() - 1) / $this->getItemsPerPage()); + + // Current page + $pages++; + + // Pages after + $pages += ceil(($this->getTotalRecords() - $this->getLastShown()) / $this->getItemsPerPage()); + + return (int) $pages; + } + + + /** + * Get the page currently active. This is calculated by adding one to the previous number + * of pages calculated via the "first shown record" position. + * + * @return int + */ + public function getCurrentPage() + { + return (int) ceil(($this->getFirstShown() - 1) / $this->getItemsPerPage()) + 1; + } + + /** + * Get the next page number + * + * @return int + */ + public function getNextPage() + { + return min($this->getTotalPages(), $this->getCurrentPage() + 1); + } + + /** + * Get the previous page number + * + * @return int + */ + public function getPreviousPage() + { + return max(1, $this->getCurrentPage() - 1); + } + + /** + * Set the page sizes to use in the "Show x" dropdown + * + * @param array $pageSizes + * @return $this + */ + public function setPageSizes(array $pageSizes) + { + $this->pageSizes = $pageSizes; + return $this; + } + + /** + * Get the sizes for the "Show x" dropdown + * + * @return array + */ + public function getPageSizes() + { + return $this->pageSizes; + } + + /** + * Gets a list of page sizes for use in templates as a dropdown + * + * @return ArrayList + */ + public function getPageSizesAsList() + { + $pageSizes = ArrayList::create(); + $perPage = $this->getItemsPerPage(); + foreach ($this->getPageSizes() as $pageSize) { + $pageSizes->push(array( + 'Size' => $pageSize, + 'Selected' => $pageSize == $perPage + )); + } + return $pageSizes; + } + + /** + * Get the GridField used in this request + * + * @return GridField + * @throws Exception If the GridField has not been previously set + */ + public function getGridField() + { + if ($this->gridField) { + return $this->gridField; + } + throw new Exception('No GridField available yet for this request!'); + } + + /** + * Set the GridField so it can be used in other parts of the component during this request + * + * @param GridField $gridField + * @return $this + */ + public function setGridField(GridField $gridField) + { + $this->gridField = $gridField; + return $this; + } + + public function handleAction(GridField $gridField, $actionName, $arguments, $data) + { + $this->setGridField($gridField); + + if ($actionName !== 'paginate') { + return; + } + $state = $this->getGridPagerState(); + + $state->firstShown = (int) $arguments['first-shown']; + $state->pageSize = $data[$gridField->getName()]['page-sizes']; + + $this->setItemsPerPage($state->pageSize); + } + + public function getManipulatedData(GridField $gridField, SS_List $dataList) + { + // Assign the GridField to the class so it can be used later in the request + $this->setGridField($gridField); + + if (!($dataList instanceof SS_Limitable) || ($dataList instanceof UnsavedRelationList)) { + return $dataList; + } + + return $dataList->limit($this->getItemsPerPage(), $this->getFirstShown() - 1); + } + + /** + * Add the configurable page size options to the template data + * + * {@inheritDoc} + * + * @param GridField $gridField + * @return ArrayList|null + */ + public function getTemplateParameters(GridField $gridField) + { + $state = $this->getGridPagerState(); + $arguments = $this->getPagerArguments(); + + // Figure out which page and record range we're on + if (!$arguments['total-rows']) { + return; + } + + // If there is only 1 page for all the records in list, we don't need to go further to sort out those + // first page, last page, pre and next pages, etc we are not render those in to the paginator. + if ($arguments['total-pages'] == 1) { + return ArrayData::create(array( + 'OnlyOnePage' => true, + 'FirstShownRecord' => $arguments['first-shown'], + 'LastShownRecord' => $arguments['last-shown'], + 'NumRecords' => $arguments['total-rows'], + 'NumPages' => $arguments['total-pages'] + )); + } + + // Define a list of the FormActions that should be generated for pager controls (see getPagerActions()) + $controls = array( + 'first' => array( + 'title' => 'First', + 'args' => array('first-shown' => 1), + 'extra-class' => 'ss-gridfield-firstpage', + 'disable-previous' => ($this->getCurrentPage() == 1) + ), + 'prev' => array( + 'title' => 'Previous', + 'args' => array('first-shown' => $arguments['first-shown'] - $this->getItemsPerPage()), + 'extra-class' => 'ss-gridfield-previouspage', + 'disable-previous' => ($this->getCurrentPage() == 1) + ), + 'next' => array( + 'title' => 'Next', + 'args' => array('first-shown' => $arguments['first-shown'] + $this->getItemsPerPage()), + 'extra-class' => 'ss-gridfield-nextpage', + 'disable-next' => ($this->getCurrentPage() == $arguments['total-pages']) + ), + 'last' => array( + 'title' => 'Last', + 'args' => array('first-shown' => ($this->getTotalPages() - 1) * $this->getItemsPerPage() + 1), + 'extra-class' => 'ss-gridfield-lastpage', + 'disable-next' => ($this->getCurrentPage() == $arguments['total-pages']) + ), + 'pagesize' => array( + 'title' => 'Page Size', + 'args' => array('first-shown' => $arguments['first-shown']), + 'extra-class' => 'ss-gridfield-pagesize-submit' + ), + ); + + if ($controls['prev']['args']['first-shown'] < 1) { + $controls['prev']['args']['first-shown'] = 1; + } + + $actions = $this->getPagerActions($controls, $gridField); + + // Render in template + return ArrayData::create(array( + 'OnlyOnePage' => false, + 'FirstPage' => $actions['first'], + 'PreviousPage' => $actions['prev'], + 'NextPage' => $actions['next'], + 'LastPage' => $actions['last'], + 'PageSizesSubmit' => $actions['pagesize'], + 'CurrentPageNum' => $this->getCurrentPage(), + 'NumPages' => $arguments['total-pages'], + 'FirstShownRecord' => $arguments['first-shown'], + 'LastShownRecord' => $arguments['last-shown'], + 'NumRecords' => $arguments['total-rows'], + 'PageSizes' => $this->getPageSizesAsList(), + 'PageSizesName' => $gridField->getName() . '[page-sizes]', + )); + } + + public function getHTMLFragments($gridField) + { + GridFieldExtensions::include_requirements(); + + $gridField->addExtraClass('ss-gridfield-configurable-paginator'); + + $forTemplate = $this->getTemplateParameters($gridField); + if ($forTemplate) { + return array( + 'footer' => $forTemplate->renderWith( + $this->itemClass, + array('Colspan' => count($gridField->getColumns())) + ) + ); + } + } + + /** + * Returns an array containing the arguments for the pagination: total rows, pages, first record etc + * + * @return array + */ + protected function getPagerArguments() + { + return array( + 'total-rows' => $this->getTotalRecords(), + 'total-pages' => $this->getTotalPages(), + 'items-per-page' => $this->getItemsPerPage(), + 'first-shown' => $this->getFirstShown(), + 'last-shown' => $this->getLastShown(), + ); + } + + /** + * Returns FormActions for each of the pagination actions, in an array + * + * @param array $controls + * @param GridField $gridField + * @return GridField_FormAction[] + */ + public function getPagerActions(array $controls, GridField $gridField) + { + $actions = array(); + + foreach ($controls as $key => $arguments) { + $action = GridField_FormAction::create( + $gridField, + 'pagination_' . $key, + $arguments['title'], + 'paginate', + $arguments['args'] + ); + + if (isset($arguments['extra-class'])) { + $action->addExtraClass($arguments['extra-class']); + } + + if (isset($arguments['disable-previous']) && $arguments['disable-previous']) { + $action = $action->performDisabledTransformation(); + } elseif (isset($arguments['disable-next']) && $arguments['disable-next']) { + $action = $action->performDisabledTransformation(); + } + + $actions[$key] = $action; + } + + return $actions; + } + + public function getActions($gridField) + { + return array('paginate'); + } + + /** + * Gets the state from the current request's GridField and sets some default values on it + * + * @param GridField $gridField Not used, but present for parent method compatibility + * @return GridState_Data + */ + protected function getGridPagerState(GridField $gridField = null) + { + if (!$this->gridFieldState) { + $state = $this->getGridField()->State->GridFieldConfigurablePaginator; + + // SS 3.1 compatibility (missing __call) + if (is_object($state->firstShown)) { + $state->firstShown = 1; + } + if (is_object($state->pageSize)) { + $state->pageSize = $this->getItemsPerPage(); + } + + // Handle free input in the page number field + $parentState = $this->getGridField()->State->GridFieldPaginator; + if (is_object($parentState->currentPage)) { + $parentState->currentPage = 0; + } + + if ($parentState->currentPage >= 1) { + $state->firstShown = ($parentState->currentPage - 1) * $this->getItemsPerPage() + 1; + $parentState->currentPage = null; + } + + $this->gridFieldState = $state; + } + + return $this->gridFieldState; + } +} diff --git a/css/GridFieldExtensions.css b/css/GridFieldExtensions.css index d2a3038..a3d11c5 100644 --- a/css/GridFieldExtensions.css +++ b/css/GridFieldExtensions.css @@ -1,184 +1,206 @@ -/** - * GridFieldAddExistingSearchButton - */ - -.add-existing-search-dialog { - min-width: inherit !important; -} - -.add-existing-search-dialog .add-existing-search-form .field { - border: none; - box-shadow: none; - margin-bottom: 0; - padding-bottom: 0; -} - -.add-existing-search-dialog .add-existing-search-form .field label { - padding-bottom: 4px; -} - -.add-existing-search-dialog .add-existing-search-form .Actions { - margin-top: 10px; - padding: 0; -} - -.add-existing-search-dialog .add-existing-search-items li a { - background: #FFF; - border-bottom-width: 1px; - border-color: #CCC; - border-left-width: 1px; - border-right-width: 1px; - border-style: solid; - display: block; - padding: 6px; -} - -.add-existing-search-dialog .add-existing-search-items li:first-child a { - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-top-width: 1px; -} - -.add-existing-search-dialog .add-existing-search-items li:last-child a { - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; -} - -.add-existing-search-dialog .add-existing-search-items li a:hover { - background: #F4F4F4; -} - -.add-existing-search-dialog .add-existing-search-pagination li { - background: #FFF; - display: block; - float: left; - margin-right: 2px; - margin-top: 12px; - padding: 6px; -} - -/** - * GridFieldAddNewInlineButton - */ - -.ss-gridfield-inline-new { - background: #EFE; -} - -.ss-gridfield-inline-new:nth-child(2n) { - background: #DFD; -} - -/** - * GridFieldAddNewMultiClass - */ - -.ss-gridfield-add-new-multi-class { - margin-bottom: 8px !important; -} - -.ss-gridfield-add-new-multi-class .field { - border: none; - box-shadow: none; - float: left; - margin: 0 4px 0 0; -} - -/** - * GridFieldEditableColumns - */ - -.ss-gridfield-editable .readonly { - padding-top: 0 !important; -} - -.ss-gridfield-editable input.text, -.ss-gridfield-editable textarea, -.ss-gridfield-editable select, -.ss-gridfield-editable .TreeDropdownField { - margin: 0 !important; - max-width: none !important; -} - -.ss-gridfield-editable select.dropdown { - border: 1px solid #b3b3b3; - background-color: #fff; - padding: 7px 7px; - padding-left: 4px; - line-height: 16px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; -} - -.ss-gridfield-add-new-inline { - margin-bottom: 12px; -} - -.ss-gridfield-add-new-inline span.readonly { - color: #FFF !important; -} - -.ss-gridfield-add-new-inline .col-buttons { - text-align: right; -} - -/** - * GridFieldOrderableRows - */ - -.ss-gridfield-orderable thead tr th.col-Reorder span { - padding: 0 !important; - margin-left: 8px; -} - -.ss-gridfield-orderable .col-reorder { - position: relative; - padding: 0 !important; - width: 16px !important; -} - -.ss-gridfield-orderable .col-reorder .handle { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - cursor: move; -} - -.ss-gridfield-orderable .col-reorder .handle .icon { - position: absolute; - top: 50%; - left: 50%; - width: 5px; - height: 11px; - margin: -5px 0 0 -2px; - background-image: url('../../framework/thirdparty/jquery-ui-themes/smoothness/images/ui-icons_222222_256x240.png'); - background-position: -5px -227px; -} - -.ss-gridfield-orderhelper { - border-bottom: 1px solid rgba(0, 0, 0, .1); - border-top: 1px solid rgba(0, 0, 0, .1); - box-shadow: 0 0 8px rgba(0, 0, 0, .4); -} - -.ss-gridfield-orderable tfoot .ui-droppable { - padding-left: 12px; - padding-right: 12px; -} - -.ss-gridfield-orderable tfoot .ui-droppable-active { - background-color: #D4CF90 !important; -} - -.ss-gridfield-orderable tfoot .ss-gridfield-previouspage { - background-position: -16px 9px !important; - margin-left: 0; -} - -.ss-gridfield-orderable tfoot .ss-gridfield-nextpage { - background-position: -40px 9px !important; - margin-right: 0; -} +/** + * GridFieldAddExistingSearchButton + */ + +.add-existing-search-dialog { + min-width: inherit !important; +} + +.add-existing-search-dialog .add-existing-search-form .field { + border: none; + box-shadow: none; + margin-bottom: 0; + padding-bottom: 0; +} + +.add-existing-search-dialog .add-existing-search-form .field label { + padding-bottom: 4px; +} + +.add-existing-search-dialog .add-existing-search-form .Actions { + margin-top: 10px; + padding: 0; +} + +.add-existing-search-dialog .add-existing-search-items li a { + background: #FFF; + border-bottom-width: 1px; + border-color: #CCC; + border-left-width: 1px; + border-right-width: 1px; + border-style: solid; + display: block; + padding: 6px; +} + +.add-existing-search-dialog .add-existing-search-items li:first-child a { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-top-width: 1px; +} + +.add-existing-search-dialog .add-existing-search-items li:last-child a { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + +.add-existing-search-dialog .add-existing-search-items li a:hover { + background: #F4F4F4; +} + +.add-existing-search-dialog .add-existing-search-pagination li { + background: #FFF; + display: block; + float: left; + margin-right: 2px; + margin-top: 12px; + padding: 6px; +} + +/** + * GridFieldAddNewInlineButton + */ + +.ss-gridfield-inline-new { + background: #EFE; +} + +.ss-gridfield-inline-new:nth-child(2n) { + background: #DFD; +} + +/** + * GridFieldAddNewMultiClass + */ + +.ss-gridfield-add-new-multi-class { + margin-bottom: 8px !important; +} + +.ss-gridfield-add-new-multi-class .field { + border: none; + box-shadow: none; + float: left; + margin: 0 4px 0 0; +} + +/** + * GridFieldEditableColumns + */ + +.ss-gridfield-editable .readonly { + padding-top: 0 !important; +} + +.ss-gridfield-editable input.text, +.ss-gridfield-editable textarea, +.ss-gridfield-editable select, +.ss-gridfield-editable .TreeDropdownField { + margin: 0 !important; + max-width: none !important; +} + +.ss-gridfield-editable select.dropdown { + border: 1px solid #b3b3b3; + background-color: #fff; + padding: 7px 7px; + padding-left: 4px; + line-height: 16px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} + +.ss-gridfield-add-new-inline { + margin-bottom: 12px; +} + +.ss-gridfield-add-new-inline span.readonly { + color: #FFF !important; +} + +.ss-gridfield-add-new-inline .col-buttons { + text-align: right; +} + +/** + * GridFieldOrderableRows + */ + +.ss-gridfield-orderable thead tr th.col-Reorder span { + padding: 0 !important; + margin-left: 8px; +} + +.ss-gridfield-orderable .col-reorder { + position: relative; + padding: 0 !important; + width: 16px !important; +} + +.ss-gridfield-orderable .col-reorder .handle { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + cursor: move; +} + +.ss-gridfield-orderable .col-reorder .handle .icon { + position: absolute; + top: 50%; + left: 50%; + width: 5px; + height: 11px; + margin: -5px 0 0 -2px; + background-image: url('../../framework/thirdparty/jquery-ui-themes/smoothness/images/ui-icons_222222_256x240.png'); + background-position: -5px -227px; +} + +.ss-gridfield-orderhelper { + border-bottom: 1px solid rgba(0, 0, 0, .1); + border-top: 1px solid rgba(0, 0, 0, .1); + box-shadow: 0 0 8px rgba(0, 0, 0, .4); +} + +.ss-gridfield-orderable tfoot .ui-droppable { + padding-left: 12px; + padding-right: 12px; +} + +.ss-gridfield-orderable tfoot .ui-droppable-active { + background-color: #D4CF90 !important; +} + +.ss-gridfield-orderable tfoot .ss-gridfield-previouspage { + background-position: -16px 9px !important; + margin-left: 0; +} + +.ss-gridfield-orderable tfoot .ss-gridfield-nextpage { + background-position: -40px 9px !important; + margin-right: 0; +} + +/** + * GridFieldConfigurablePaginator + */ +.ss-gridfield-configurable-paginator .pagination-page-size { + color: #fff; + float: left; + padding: 6px 0; +} + +.ss-gridfield-configurable-paginator .pagination-page-size-select { + margin-left: 0; + width: auto; +} + +.ss-gridfield-configurable-paginator .ss-gridfield-pagesize-submit { + display: none; +} + +.ss-gridfield-configurable-paginator .pagination-page-number input { + text-align: center; +} diff --git a/javascript/GridFieldExtensions.js b/javascript/GridFieldExtensions.js index 435cdb6..ff2f230 100644 --- a/javascript/GridFieldExtensions.js +++ b/javascript/GridFieldExtensions.js @@ -387,5 +387,14 @@ if(this.hasClass("ui-droppable")) this.droppable("destroy"); } }); + + /** + * GridFieldConfigurablePaginator + */ + $('.ss-gridfield-configurable-paginator .pagination-page-size-select').entwine({ + onchange: function () { + this.parent().find('.ss-gridfield-pagesize-submit').trigger('click'); + } + }); }); })(jQuery); diff --git a/templates/GridFieldConfigurablePaginator.ss b/templates/GridFieldConfigurablePaginator.ss new file mode 100644 index 0000000..f1229b2 --- /dev/null +++ b/templates/GridFieldConfigurablePaginator.ss @@ -0,0 +1,30 @@ + + + <% if not $OnlyOnePage %> + + <%t GridFieldConfigurablePaginator.SHOW 'Show' %> + + $PageSizesSubmit + +
+ $FirstPage $PreviousPage + + <%t Pagination.Page 'Page' %> + + <%t TableListField_PageControls_ss.OF 'of' is 'Example: View 1 of 2' %> + $NumPages + + $NextPage $LastPage +
+ <% end_if %> + + {$FirstShownRecord}–{$LastShownRecord} + <%t TableListField_PageControls_ss.OF 'of' is 'Example: View 1 of 2' %> + $NumRecords + + + diff --git a/tests/GridFieldConfigurablePaginatorTest.php b/tests/GridFieldConfigurablePaginatorTest.php new file mode 100644 index 0000000..cd58440 --- /dev/null +++ b/tests/GridFieldConfigurablePaginatorTest.php @@ -0,0 +1,217 @@ +push(array('ID' => $i)); + } + + $this->gridField = GridField::create('Mock', null, $data); + } + + public function testGetTotalRecords() + { + $paginator = new GridFieldConfigurablePaginator; + $paginator->setGridField($this->gridField); + + $this->assertSame(130, $paginator->getTotalRecords()); + } + + public function testGetFirstShown() + { + $paginator = new GridFieldConfigurablePaginator; + $paginator->setGridField($this->gridField); + + // No state + $this->assertSame(1, $paginator->getFirstShown()); + + // With a state + $paginator->setFirstShown(123); + $this->assertSame(123, $paginator->getFirstShown()); + + // Too high! + $paginator->setFirstShown(234); + $this->assertSame(1, $paginator->getFirstShown()); + } + + public function testGetLastShown() + { + $paginator = new GridFieldConfigurablePaginator(20, array(10, 20, 30)); + $paginator->setGridField($this->gridField); + + $this->assertSame(20, $paginator->getLastShown()); + + $paginator->setFirstShown(5); + $this->assertSame(24, $paginator->getLastShown()); + } + + public function testGetTotalPages() + { + $paginator = new GridFieldConfigurablePaginator(20, array(20, 40, 60)); + $paginator->setGridField($this->gridField); + + // Default calculation + $this->assertSame(7, $paginator->getTotalPages()); + + // With a standard "first shown" record number, e.g. page 2 + $paginator->setFirstShown(21); + $this->assertSame(7, $paginator->getTotalPages()); + + // Non-standard "first shown", e.g. when a page size is changed at page 3. In this case the first page is + // 20 records, the second page is 7 records, third page 20 records, etc + $paginator->setFirstShown(27); + $this->assertSame(8, $paginator->getTotalPages()); + + // ... and when the page size has also been changed. In this case the first page is 57 records, second page + // 60 records and last page is 13 records + $paginator->setFirstShown(57); + $paginator->setItemsPerPage(60); + $this->assertSame(3, $paginator->getTotalPages()); + } + + public function testGetCurrentPreviousAndNextPages() + { + $paginator = new GridFieldConfigurablePaginator(20, array(20, 40, 60)); + $paginator->setGridField($this->gridField); + + // No page selected (first page) + $this->assertSame(1, $paginator->getCurrentPage()); + $this->assertSame(1, $paginator->getPreviousPage()); + $this->assertSame(2, $paginator->getNextPage()); + + // Second page + $paginator->setFirstShown(21); + $this->assertSame(2, $paginator->getCurrentPage()); + $this->assertSame(1, $paginator->getPreviousPage()); + $this->assertSame(3, $paginator->getNextPage()); + + // Third page + $paginator->setFirstShown(41); + $this->assertSame(3, $paginator->getCurrentPage()); + $this->assertSame(2, $paginator->getPreviousPage()); + $this->assertSame(4, $paginator->getNextPage()); + + // Fourth page, partial record count + $paginator->setFirstShown(42); + $this->assertSame(4, $paginator->getCurrentPage()); + $this->assertSame(3, $paginator->getPreviousPage()); + $this->assertSame(5, $paginator->getNextPage()); + + // Last page (default paging) + $paginator->setFirstShown(121); + $this->assertSame(7, $paginator->getCurrentPage()); + $this->assertSame(6, $paginator->getPreviousPage()); + $this->assertSame(7, $paginator->getNextPage()); + + // Non-standard page size should recalculate the page numbers to be relative to the page size + $paginator->setFirstShown(121); + $paginator->setItemsPerPage(60); + $this->assertSame(3, $paginator->getCurrentPage()); + $this->assertSame(2, $paginator->getPreviousPage()); + $this->assertSame(3, $paginator->getNextPage()); + } + + public function testPageSizesAreConfigurable() + { + // Via constructor + $paginator = new GridFieldConfigurablePaginator(3, array(2, 4, 6)); + $this->assertSame(3, $paginator->getItemsPerPage()); + $this->assertSame(array(2, 4, 6), $paginator->getPageSizes()); + + // Via public API + $paginator->setPageSizes(array(10, 20, 30)); + $this->assertSame(array(10, 20, 30), $paginator->getPageSizes()); + + // Via default configuration + $paginator = new GridFieldConfigurablePaginator; + $default = Config::inst()->get('GridFieldConfigurablePaginator', 'default_page_sizes'); + $this->assertSame($default, $paginator->getPageSizes()); + } + + public function testGetPageSizesAsList() + { + $paginator = new GridFieldConfigurablePaginator(10, array(10, 20, 30)); + $this->assertDOSEquals(array( + array('Size' => '10', 'Selected' => true), + array('Size' => '20', 'Selected' => false), + array('Size' => '30', 'Selected' => false), + ), $paginator->getPageSizesAsList()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage No GridField available yet for this request! + */ + public function testGetGridFieldThrowsExceptionWhenNotSet() + { + $paginator = new GridFieldConfigurablePaginator; + $paginator->getGridField(); + } + + public function testGetPagerActions() + { + $controls = array( + 'prev' => array( + 'title' => 'Previous', + 'args' => array( + 'next-page' => 123, + 'first-shown' => 234 + ), + 'extra-class' => 'ss-gridfield-previouspage', + 'disable-previous' => false + ), + 'next' => array( + 'title' => 'Next', + 'args' => array( + 'next-page' => 234, + 'first-shown' => 123 + ), + 'extra-class' => 'ss-gridfield-nextpage', + 'disable-next' => true + ) + ); + + $gridField = $this->getMockBuilder('GridField')->disableOriginalConstructor()->getMock(); + $paginator = new GridFieldConfigurablePaginator; + $result = $paginator->getPagerActions($controls, $gridField); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('next', $result); + $this->assertContainsOnlyInstancesOf('GridField_FormAction', $result); + + $this->assertFalse($result['prev']->isDisabled()); + + $this->assertTrue((bool) $result['next']->hasClass('ss-gridfield-nextpage')); + $this->assertTrue($result['next']->isDisabled()); + } + + public function testSinglePageWithLotsOfItems() + { + $paginator = new GridFieldConfigurablePaginator(null, array(100, 200, 300)); + $this->assertSame(100, $paginator->getItemsPerPage()); + } + + /** + * Set something to the GridField's paginator state data + * + * @param string $key + * @param mixed $value + * @return $this + */ + protected function setState($key, $value) + { + $this->gridField->State->GridFieldConfigurablePaginator->$key = $value; + return $this; + } +} From f0d2ec773395a71bd8ca90c72d596b89a6103027 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Tue, 1 Aug 2017 16:41:17 +1200 Subject: [PATCH 03/12] FIX Always show page size dropdown when page size is greater than number of records --- code/GridFieldConfigurablePaginator.php | 14 +------------- templates/GridFieldConfigurablePaginator.ss | 18 +++++++++--------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/code/GridFieldConfigurablePaginator.php b/code/GridFieldConfigurablePaginator.php index 6c785f9..817685c 100644 --- a/code/GridFieldConfigurablePaginator.php +++ b/code/GridFieldConfigurablePaginator.php @@ -261,18 +261,6 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator return; } - // If there is only 1 page for all the records in list, we don't need to go further to sort out those - // first page, last page, pre and next pages, etc we are not render those in to the paginator. - if ($arguments['total-pages'] == 1) { - return ArrayData::create(array( - 'OnlyOnePage' => true, - 'FirstShownRecord' => $arguments['first-shown'], - 'LastShownRecord' => $arguments['last-shown'], - 'NumRecords' => $arguments['total-rows'], - 'NumPages' => $arguments['total-pages'] - )); - } - // Define a list of the FormActions that should be generated for pager controls (see getPagerActions()) $controls = array( 'first' => array( @@ -314,7 +302,7 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator // Render in template return ArrayData::create(array( - 'OnlyOnePage' => false, + 'OnlyOnePage' => ($arguments['total-pages'] == 1), 'FirstPage' => $actions['first'], 'PreviousPage' => $actions['prev'], 'NextPage' => $actions['next'], diff --git a/templates/GridFieldConfigurablePaginator.ss b/templates/GridFieldConfigurablePaginator.ss index f1229b2..adb2490 100644 --- a/templates/GridFieldConfigurablePaginator.ss +++ b/templates/GridFieldConfigurablePaginator.ss @@ -1,15 +1,15 @@ + + <%t GridFieldConfigurablePaginator.SHOW 'Show' %> + + $PageSizesSubmit + <% if not $OnlyOnePage %> - - <%t GridFieldConfigurablePaginator.SHOW 'Show' %> - - $PageSizesSubmit -
$FirstPage $PreviousPage From 403900f1fd86e8896c04586c42e454e7d4de0aa3 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Wed, 2 Aug 2017 09:28:31 +1200 Subject: [PATCH 04/12] FIX Ensure selected page size is retained when deleting records --- code/GridFieldConfigurablePaginator.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/code/GridFieldConfigurablePaginator.php b/code/GridFieldConfigurablePaginator.php index 817685c..c147e09 100644 --- a/code/GridFieldConfigurablePaginator.php +++ b/code/GridFieldConfigurablePaginator.php @@ -236,6 +236,12 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator // Assign the GridField to the class so it can be used later in the request $this->setGridField($gridField); + // Retain page sizes during actions provided by other components + $state = $this->getGridPagerState(); + if (is_numeric($state->pageSize)) { + $this->setItemsPerPage($state->pageSize); + } + if (!($dataList instanceof SS_Limitable) || ($dataList instanceof UnsavedRelationList)) { return $dataList; } @@ -254,6 +260,9 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator public function getTemplateParameters(GridField $gridField) { $state = $this->getGridPagerState(); + if (is_numeric($state->pageSize)) { + $this->setItemsPerPage($state->pageSize); + } $arguments = $this->getPagerArguments(); // Figure out which page and record range we're on From 36d9711a4704320666cb849f920fcfad194480cf Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Wed, 2 Aug 2017 10:03:00 +1200 Subject: [PATCH 05/12] Use precise distro for Travis builds to allow PHP 5.3 to work --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8425684..fceb781 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ sudo: false language: php +dist: precise + php: - 5.3 - 5.4 From 719b906460c80e476c882af6104d6a3918223881 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Wed, 23 Aug 2017 14:07:42 +1200 Subject: [PATCH 06/12] DOCS Add use examples for GridFieldConfigurablePaginator --- README.md | 51 ++++++++++++++++++++++++------------------------ docs/en/index.md | 33 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index fda41b7..b5589c1 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,26 @@ -SilverStripe Grid Field Extensions Module -========================================= - -[![Build Status](https://travis-ci.org/silverstripe-australia/silverstripe-gridfieldextensions.svg?branch=master)](https://travis-ci.org/silverstripe-australia/silverstripe-gridfieldextensions) -[![Latest Stable Version](https://poser.pugx.org/silverstripe-australia/gridfieldextensions/version.svg)](https://github.com/silverstripe-australia/silverstripe-gridfieldextensions/releases) -[![Latest Unstable Version](https://poser.pugx.org/silverstripe-australia/gridfieldextensions/v/unstable.svg)](https://packagist.org/packages/silverstripe-australia/gridfieldextensions) -[![Total Downloads](https://poser.pugx.org/silverstripe-australia/gridfieldextensions/downloads.svg)](https://packagist.org/packages/silverstripe-australia/gridfieldextensions) -[![License](https://poser.pugx.org/silverstripe-australia/gridfieldextensions/license.svg)](https://github.com/silverstripe-australia/silverstripe-gridfieldextensions/blob/master/LICENSE.md) - -This module provides a number of useful grid field components: - -* `GridFieldAddExistingSearchButton` - a more advanced search form for adding - items. -* `GridFieldAddNewInlineButton` - builds on `GridFieldEditableColumns` to allow - inline creation of records. -* `GridFieldAddNewMultiClass` - lets the user select from a list of classes to - create a new record from. -* `GridFieldEditableColumns` - allows inline editing of records. -* `GridFieldOrderableRows` - drag and drop re-ordering of rows. -* `GridFieldRequestHandler` - a basic utility class which can be used to build - custom grid field detail views including tabs, breadcrumbs and other CMS - features. -* `GridFieldTitleHeader` - a simple header which displays column titles. - -See [docs/en/index.md](docs/en/index.md) for documentation and examples. +SilverStripe Grid Field Extensions Module +========================================= + +[![Build Status](https://travis-ci.org/silverstripe-australia/silverstripe-gridfieldextensions.svg?branch=master)](https://travis-ci.org/silverstripe-australia/silverstripe-gridfieldextensions) +[![Latest Stable Version](https://poser.pugx.org/silverstripe-australia/gridfieldextensions/version.svg)](https://github.com/silverstripe-australia/silverstripe-gridfieldextensions/releases) +[![Latest Unstable Version](https://poser.pugx.org/silverstripe-australia/gridfieldextensions/v/unstable.svg)](https://packagist.org/packages/silverstripe-australia/gridfieldextensions) +[![Total Downloads](https://poser.pugx.org/silverstripe-australia/gridfieldextensions/downloads.svg)](https://packagist.org/packages/silverstripe-australia/gridfieldextensions) +[![License](https://poser.pugx.org/silverstripe-australia/gridfieldextensions/license.svg)](https://github.com/silverstripe-australia/silverstripe-gridfieldextensions/blob/master/LICENSE.md) + +This module provides a number of useful grid field components: + +* `GridFieldAddExistingSearchButton` - a more advanced search form for adding + items. +* `GridFieldAddNewInlineButton` - builds on `GridFieldEditableColumns` to allow + inline creation of records. +* `GridFieldAddNewMultiClass` - lets the user select from a list of classes to + create a new record from. +* `GridFieldEditableColumns` - allows inline editing of records. +* `GridFieldOrderableRows` - drag and drop re-ordering of rows. +* `GridFieldRequestHandler` - a basic utility class which can be used to build + custom grid field detail views including tabs, breadcrumbs and other CMS + features. +* `GridFieldTitleHeader` - a simple header which displays column titles. +* `GridFieldConfigurablePaginator` - a paginator for GridField that allows customisable page sizes. + +See [docs/en/index.md](docs/en/index.md) for documentation and examples. diff --git a/docs/en/index.md b/docs/en/index.md index fb1e3be..5afe558 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -106,3 +106,36 @@ class Item extends DataObject { **Please NOTE:** There is a limitation when using `GridFieldOrderableRows` on unsaved data objects; namely, that it doesn't work as without data being saved, the list of related objects has no context. Please check `$this->ID` before adding the `GridFieldOrderableRows` component to the grid field config (or even, before adding the gridfield at all). +Configurable Paginator +---------------------- + +The `GridFieldConfigurablePaginator` component allows you to have a page size dropdown added to your GridField +pagination controls. The page sizes are configurable via the configuration system, or at call time using the public API. +To use this component you should remove the original paginator component first: + +```php +$gridField->getConfig() + ->removeComponentsByType('GridFieldPaginator') + ->addComponent(new GridFieldConfigurablePaginator()); +``` + +You can configure the page sizes with the configuration system. Note that merging is the default strategy, so to replace +the default sizes with your own you will need to unset the original first, for example: + +```php +# File: mysite/_config.php +Config::inst()->remove('GridFieldConfigurablePaginator', 'default_page_sizes'); +Config::inst()->update('GridFieldConfigurablePaginator', 'default_page_sizes', array(100, 200, 500)); +``` + +You can also override these at call time: + +```php +$paginator = new GridFieldConfigurablePaginator(100, array(100, 200, 500)); + +$paginator->setPageSizes(array(200, 500, 1000)); +$paginator->setItemsPerPage(500); +``` + +The first shown record will be maintained across page size changes, and the number of pages and current page will be +recalculated on each request, based on the current first shown record and page size. From e14ed0b47aec0b8deb34f83653ac4dc089d07a34 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Mon, 28 Aug 2017 14:04:20 +1200 Subject: [PATCH 07/12] Update vendor and create new major 2.x branch --- composer.json | 75 ++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/composer.json b/composer.json index 4f171c4..5b0b50b 100644 --- a/composer.json +++ b/composer.json @@ -1,37 +1,38 @@ -{ - "name": "silverstripe-australia/gridfieldextensions", - "description": "A collection of useful grid field components", - "type": "silverstripe-module", - "homepage": "http://github.com/silverstripe-australia/silverstripe-gridfieldextensions", - "keywords": ["silverstripe", "gridfield"], - "license": "BSD-3-Clause", - "authors": [ - { - "name": "Andrew Short", - "email": "andrewjshort@gmail.com" - }, - { - "name": "Marcus Nyeholt", - "email": "marcus@silverstripe.com.au" - } - ], - "support": { - "issues": "http://github.com/silverstripe-australia/silverstripe-gridfieldextensions/issues" - }, - "require": { - "silverstripe/framework": "~3.1" - }, - "extra": { - "installer-name": "gridfieldextensions", - "branch-alias": { - "dev-master": "1.4.x-dev" - }, - "screenshots": [ - "docs/en/_images/editable-rows.png", - "docs/en/_images/add-existing-search.png" - ] - }, - "replace": { - "ajshort/silverstripe-gridfieldextensions": "self.version" - } -} +{ + "name": "symbiote/silverstripe-gridfieldextensions", + "description": "A collection of useful grid field components", + "type": "silverstripe-module", + "homepage": "http://github.com/silverstripe-australia/silverstripe-gridfieldextensions", + "keywords": ["silverstripe", "gridfield"], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Andrew Short", + "email": "andrewjshort@gmail.com" + }, + { + "name": "Marcus Nyeholt", + "email": "marcus@silverstripe.com.au" + } + ], + "support": { + "issues": "http://github.com/symbiote/silverstripe-gridfieldextensions/issues" + }, + "require": { + "silverstripe/framework": "~3.1" + }, + "extra": { + "installer-name": "gridfieldextensions", + "branch-alias": { + "2.x-dev": "2.0.x-dev" + }, + "screenshots": [ + "docs/en/_images/editable-rows.png", + "docs/en/_images/add-existing-search.png" + ] + }, + "replace": { + "silverstripe-australia/gridfieldextensions": "self.version", + "ajshort/silverstripe-gridfieldextensions": "self.version" + } +} From 320a6198e5955522c915ebb232223d5358fb4f31 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Thu, 31 Aug 2017 14:35:52 +1200 Subject: [PATCH 08/12] FIX When setting the page sizes, reset items per page to the first value --- code/GridFieldConfigurablePaginator.php | 4 ++++ tests/GridFieldConfigurablePaginatorTest.php | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/code/GridFieldConfigurablePaginator.php b/code/GridFieldConfigurablePaginator.php index c147e09..6ddfd07 100644 --- a/code/GridFieldConfigurablePaginator.php +++ b/code/GridFieldConfigurablePaginator.php @@ -159,6 +159,10 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator public function setPageSizes(array $pageSizes) { $this->pageSizes = $pageSizes; + + // Reset items per page + $this->setItemsPerPage(current($pageSizes)); + return $this; } diff --git a/tests/GridFieldConfigurablePaginatorTest.php b/tests/GridFieldConfigurablePaginatorTest.php index cd58440..b16235a 100644 --- a/tests/GridFieldConfigurablePaginatorTest.php +++ b/tests/GridFieldConfigurablePaginatorTest.php @@ -80,6 +80,20 @@ class GridFieldConfigurablePaginatorTest extends SapphireTest $this->assertSame(3, $paginator->getTotalPages()); } + public function testItemsPerPageIsSetToFirstInPageSizesListWhenChanged() + { + $paginator = new GridFieldConfigurablePaginator(20, array(20, 40, 60)); + $paginator->setGridField($this->gridField); + + // Initial state, should be what was provided to the constructor + $this->assertSame(20, $paginator->getItemsPerPage()); + + $paginator->setPageSizes(array(50, 100, 200)); + + // Set via public API, should now be set to 50 + $this->assertSame(50, $paginator->getItemsPerPage()); + } + public function testGetCurrentPreviousAndNextPages() { $paginator = new GridFieldConfigurablePaginator(20, array(20, 40, 60)); From a36c96e692cfa9313751fb507535064e18a09690 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Thu, 31 Aug 2017 14:44:40 +1200 Subject: [PATCH 09/12] Remove obsolete branch alias --- composer.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/composer.json b/composer.json index 5b0b50b..0421c76 100644 --- a/composer.json +++ b/composer.json @@ -23,9 +23,6 @@ }, "extra": { "installer-name": "gridfieldextensions", - "branch-alias": { - "2.x-dev": "2.0.x-dev" - }, "screenshots": [ "docs/en/_images/editable-rows.png", "docs/en/_images/add-existing-search.png" From a34d6242a5100ac9b74632b47eb68a25b4e589f1 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Thu, 31 Aug 2017 14:45:42 +1200 Subject: [PATCH 10/12] Update branch alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5b0b50b..4dd634c 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "extra": { "installer-name": "gridfieldextensions", "branch-alias": { - "2.x-dev": "2.0.x-dev" + "2.x-dev": "2.1.x-dev" }, "screenshots": [ "docs/en/_images/editable-rows.png", From 58200f847fbdeeae6f593274846e939e482cbdd5 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Thu, 31 Aug 2017 14:35:52 +1200 Subject: [PATCH 11/12] FIX When setting the page sizes, reset items per page to the first value --- code/GridFieldConfigurablePaginator.php | 4 ++++ tests/GridFieldConfigurablePaginatorTest.php | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/code/GridFieldConfigurablePaginator.php b/code/GridFieldConfigurablePaginator.php index c147e09..6ddfd07 100644 --- a/code/GridFieldConfigurablePaginator.php +++ b/code/GridFieldConfigurablePaginator.php @@ -159,6 +159,10 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator public function setPageSizes(array $pageSizes) { $this->pageSizes = $pageSizes; + + // Reset items per page + $this->setItemsPerPage(current($pageSizes)); + return $this; } diff --git a/tests/GridFieldConfigurablePaginatorTest.php b/tests/GridFieldConfigurablePaginatorTest.php index cd58440..b16235a 100644 --- a/tests/GridFieldConfigurablePaginatorTest.php +++ b/tests/GridFieldConfigurablePaginatorTest.php @@ -80,6 +80,20 @@ class GridFieldConfigurablePaginatorTest extends SapphireTest $this->assertSame(3, $paginator->getTotalPages()); } + public function testItemsPerPageIsSetToFirstInPageSizesListWhenChanged() + { + $paginator = new GridFieldConfigurablePaginator(20, array(20, 40, 60)); + $paginator->setGridField($this->gridField); + + // Initial state, should be what was provided to the constructor + $this->assertSame(20, $paginator->getItemsPerPage()); + + $paginator->setPageSizes(array(50, 100, 200)); + + // Set via public API, should now be set to 50 + $this->assertSame(50, $paginator->getItemsPerPage()); + } + public function testGetCurrentPreviousAndNextPages() { $paginator = new GridFieldConfigurablePaginator(20, array(20, 40, 60)); From 04a07d505f32f3256f35c7653389198e18e0bccd Mon Sep 17 00:00:00 2001 From: Jake B Date: Mon, 4 Sep 2017 14:59:38 +1000 Subject: [PATCH 12/12] FIX Backport and sanitiseClassName for the "Save" action URL --- code/GridFieldAddNewMultiClassHandler.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/code/GridFieldAddNewMultiClassHandler.php b/code/GridFieldAddNewMultiClassHandler.php index cf99b5b..8eb41d0 100644 --- a/code/GridFieldAddNewMultiClassHandler.php +++ b/code/GridFieldAddNewMultiClassHandler.php @@ -9,9 +9,16 @@ class GridFieldAddNewMultiClassHandler extends GridFieldDetailForm_ItemRequest { return parent::Link($action); } else { return Controller::join_links( - $this->gridField->Link(), 'add-multi-class', get_class($this->record) + $this->gridField->Link(), 'add-multi-class', $this->sanitiseClassName(get_class($this->record)) ); } } + /** + * Sanitise a model class' name for inclusion in a link + * @return string + */ + protected function sanitiseClassName($class) { + return str_replace('\\', '-', $class); + } }