diff --git a/.travis.yml b/.travis.yml index 74cc970..7140b49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,16 @@ language: php -env: - global: - - COMPOSER_ROOT_VERSION="3.1.x-dev" - matrix: include: - php: 5.6 env: DB=MYSQL RECIPE_VERSION=1.0.x-dev PHPCS_TEST=1 PHPUNIT_TEST=1 - php: 7.0 - env: DB=PGSQL RECIPE_VERSION=1.1.x-dev PHPUNIT_TEST=1 + env: DB=MYSQL RECIPE_VERSION=1.1.x-dev PHPUNIT_TEST=1 - php: 7.1 - env: DB=MYSQL RECIPE_VERSION=4.2.x-dev PHPUNIT_COVERAGE_TEST=1 + env: DB=PGSQL RECIPE_VERSION=4.2.x-dev PHPUNIT_COVERAGE_TEST=1 - php: 7.2 + env: DB=MYSQL RECIPE_VERSION=4.3.x-dev PHPUNIT_TEST=1 + - php: 7.3 env: DB=MYSQL RECIPE_VERSION=4.x-dev PHPUNIT_TEST=1 before_script: @@ -20,8 +18,8 @@ before_script: - phpenv config-rm xdebug.ini - composer validate - - composer require silverstripe/recipe-core "$RECIPE_VERSION" --no-update - - if [[ $DB == PGSQL ]]; then composer require silverstripe/postgresql:2.0.x-dev --no-update; fi + - composer require silverstripe/recipe-cms:"$RECIPE_VERSION" --no-update + - if [[ $DB == PGSQL ]]; then composer require silverstripe/postgresql:2.1.x-dev --no-update; fi - composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile script: diff --git a/README.md b/README.md index 20a4e06..b93284f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # SilverStripe Grid Field Extensions Module [![Build Status](https://travis-ci.org/symbiote/silverstripe-gridfieldextensions.svg?branch=master)](https://travis-ci.org/symbiote/silverstripe-gridfieldextensions) +[![SilverStripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/) [![Latest Stable Version](https://poser.pugx.org/symbiote/silverstripe-gridfieldextensions/version.svg)](https://github.com/symbiote/silverstripe-gridfieldextensions/releases) [![Latest Unstable Version](https://poser.pugx.org/symbiote/silverstripe-gridfieldextensions/v/unstable.svg)](https://packagist.org/packages/symbiote/silverstripe-gridfieldextensions) [![Total Downloads](https://poser.pugx.org/symbiote/silverstripe-gridfieldextensions/downloads.svg)](https://packagist.org/packages/symbiote/silverstripe-gridfieldextensions) @@ -24,6 +25,11 @@ This module provides a number of useful grid field components: This branch will aim for compatibility with SilverStripe 4.x. +## Installation +```bash +composer require symbiote/silverstripe-gridfieldextensions "^3" +``` + For SilverStripe 3.x, please see the [compatible branch](https://github.com/symbiote/silverstripe-gridfieldextensions/tree/2). diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 0000000..53bf39c --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1 @@ +When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct). diff --git a/composer.json b/composer.json index caa0d23..849bb1a 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "require-dev": { "phpunit/phpunit": "^5.7", "squizlabs/php_codesniffer": "^3.0", - "silverstripe/versioned": "^1@dev" + "silverstripe/versioned": "^1" }, "extra": { "screenshots": [ @@ -35,10 +35,7 @@ "expose": [ "css", "javascript" - ], - "branch-alias": { - "3.x-dev": "3.3.x-dev" - } + ] }, "replace": { "ajshort/silverstripe-gridfieldextensions": "self.version", diff --git a/css/GridFieldExtensions.css b/css/GridFieldExtensions.css index 498d478..f56db7e 100644 --- a/css/GridFieldExtensions.css +++ b/css/GridFieldExtensions.css @@ -42,6 +42,11 @@ background: #DFD; } +.grid-field__table .form-check-input.editable-column-field { + margin-top: .9rem; + position: relative; +} + /** * GridFieldAddNewMultiClass */ diff --git a/javascript/GridFieldExtensions.js b/javascript/GridFieldExtensions.js index d196464..33e3115 100644 --- a/javascript/GridFieldExtensions.js +++ b/javascript/GridFieldExtensions.js @@ -208,7 +208,7 @@ } }); - $(".ss-gridfield-delete-inline").entwine({ + $(".grid-field .action.ss-gridfield-delete-inline").entwine({ onclick: function() { var msg = ss.i18n._t("GridFieldExtensions.CONFIRMDEL", "Are you sure you want to delete this?"); @@ -325,6 +325,12 @@ self.find('.sortable-header th:last').html(content); } + // update CMS preview + var preview = $('.cms-preview'); + if (preview.length) { + preview.entwine('.ss.preview')._initialiseFromContent(); + } + form.removeClass('loading'); if (successCallback) { successCallback.apply(this, arguments); diff --git a/src/GridFieldConfigurablePaginator.php b/src/GridFieldConfigurablePaginator.php index 3655ae9..54e5c7d 100644 --- a/src/GridFieldConfigurablePaginator.php +++ b/src/GridFieldConfigurablePaginator.php @@ -290,33 +290,34 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator 'title' => 'First', 'args' => array('first-shown' => 1), 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-left ' - . 'ss-gridfield-firstpage', + . 'ss-gridfield-pagination-action ss-gridfield-firstpage', 'disable-previous' => ($this->getCurrentPage() == 1) ), 'prev' => array( 'title' => 'Previous', 'args' => array('first-shown' => $arguments['first-shown'] - $this->getItemsPerPage()), 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-left ' - . 'ss-gridfield-previouspage', + . 'ss-gridfield-pagination-action ss-gridfield-previouspage', 'disable-previous' => ($this->getCurrentPage() == 1) ), 'next' => array( 'title' => 'Next', 'args' => array('first-shown' => $arguments['first-shown'] + $this->getItemsPerPage()), - 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-right ss-gridfield-nextpage', + 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-right ' + .'ss-gridfield-pagination-action 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' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-right ' - . 'ss-gridfield-lastpage', + . 'ss-gridfield-pagination-action 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' + 'extra-class' => 'ss-gridfield-pagination-action ss-gridfield-pagesize-submit' ), ); diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index c0c9e87..24029d9 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -4,6 +4,8 @@ namespace Symbiote\GridFieldExtensions; use Exception; use SilverStripe\Control\Controller; +use SilverStripe\Control\HTTPRequest; +use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\RequestHandler; use SilverStripe\Core\ClassInfo; use SilverStripe\Forms\GridField\GridField; @@ -20,11 +22,11 @@ use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectSchema; use SilverStripe\ORM\DB; +use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\ManyManyList; use SilverStripe\ORM\ManyManyThroughList; use SilverStripe\ORM\ManyManyThroughQueryManipulator; use SilverStripe\ORM\SS_List; -use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\Versioned\Versioned; use SilverStripe\View\ViewableData; @@ -233,20 +235,16 @@ class GridFieldOrderableRows extends RequestHandler implements public function getSortTable(SS_List $list) { $field = $this->getSortField(); - if ($list instanceof ManyManyList) { $extra = $list->getExtraFields(); $table = $list->getJoinTable(); - if ($extra && array_key_exists($field, $extra)) { return $table; } } elseif ($list instanceof ManyManyThroughList) { return $this->getManyManyInspector($list)->getJoinAlias(); } - $classes = ClassInfo::dataClassesFor($list->dataClass()); - foreach ($classes as $class) { if (singleton($class)->hasDataBaseField($field)) { return DataObject::getSchema()->tableName($class); @@ -299,7 +297,22 @@ class GridFieldOrderableRows extends RequestHandler implements $record->ID, $this->getSortField() ); - $sortField = new HiddenField($sortFieldName, false, $record->getField($this->getSortField())); + + // Default: Get the sort field directly from the current record + $currentSortValue = $record->getField($this->getSortField()); + + $list = $grid->getList(); + if ($list instanceof ManyManyThroughList) { + // In a many many through list we should get the current sort order from the relationship + // if it exists, not directly from the record + $throughListSorts = $this->getSortValuesFromManyManyThroughList($list, $this->getSortField()); + + if (array_key_exists($record->ID, $throughListSorts)) { + $currentSortValue = $throughListSorts[$record->ID]; + } + } + + $sortField = HiddenField::create($sortFieldName, false, $currentSortValue); $sortField->addExtraClass('ss-orderable-hidden-sort'); $sortField->setForm($grid->getForm()); @@ -349,17 +362,18 @@ class GridFieldOrderableRows extends RequestHandler implements $sortterm .= '"'.$this->getSortTable($list).'"."'.$this->getSortField().'"'; } return $list->sort($sortterm); - } else { - return $list; } + + return $list; } /** * Handles requests to reorder a set of IDs in a specific order. * * @param GridField $grid - * @param SS_HTTPRequest $request - * @return SS_HTTPResponse + * @param HTTPRequest $request + * @return string + * @throws HTTPResponse_Exception */ public function handleReorder($grid, $request) { @@ -540,16 +554,7 @@ class GridFieldOrderableRows extends RequestHandler implements } } } elseif ($items instanceof ManyManyThroughList) { - $manipulator = $this->getManyManyInspector($list); - $joinClass = $manipulator->getJoinClass(); - $fromRelationName = $manipulator->getForeignKey(); - $toRelationName = $manipulator->getLocalKey(); - $sortlist = DataList::create($joinClass)->filter([ - $toRelationName => $items->column('ID'), - // first() is safe as there are earlier checks to ensure our list to sort is valid - $fromRelationName => $items->first()->getJoin()->$fromRelationName, - ]); - $current = $sortlist->map($toRelationName, $sortField)->toArray(); + $current = $this->getSortValuesFromManyManyThroughList($list, $sortField); } else { $current = $items->map('ID', $sortField)->toArray(); } @@ -583,25 +588,11 @@ class GridFieldOrderableRows extends RequestHandler implements // Model has sort column and is versioned - handle as versioned // Model has sort column and is NOT versioned - handle as NOT versioned // Model doesn't have sort column because sort column is on ManyManyList - handle as NOT versioned - // Model doesn't have sort column because sort column is on ManyManyThroughList... - // - Related item is not versioned: - // - Through object is versioned: THROW an error. - // - Through object is NOT versioned: handle as NOT versioned - // - Related item is versioned... - // - Through object is versioned: handle as versioned - // - Through object is NOT versioned: THROW an error. + // Model doesn't have sort column because sort column is on ManyManyThroughList - inspect through object if ($list instanceof ManyManyThroughList) { - $listClassVersioned = $class::create()->hasExtension(Versioned::class); // We'll be updating the join class, not the relation class. $class = $this->getManyManyInspector($list)->getJoinClass(); $isVersioned = $class::create()->hasExtension(Versioned::class); - - // If one side of the relationship is versioned and the other is not, throw an error. - if ($listClassVersioned xor $isVersioned) { - throw new Exception( - 'ManyManyThrough cannot mismatch Versioning between join class and related class' - ); - } } elseif (!$this->isManyMany($list)) { $isVersioned = $class::create()->hasExtension(Versioned::class); } @@ -656,7 +647,7 @@ class GridFieldOrderableRows extends RequestHandler implements } } - $this->extend('onAfterReorderItems', $list); + $this->extend('onAfterReorderItems', $list, $values, $sortedIDs); } protected function populateSortValues(DataList $list) @@ -763,4 +754,30 @@ class GridFieldOrderableRows extends RequestHandler implements } return $inspector; } + + /** + * Used to get sort orders from a many many through list relationship record, rather than the current + * record itself. + * + * @param ManyManyList|ManyManyThroughList $list + * @return int[] Sort orders for the + */ + protected function getSortValuesFromManyManyThroughList($list, $sortField) + { + $manipulator = $this->getManyManyInspector($list); + + // Find the foreign key name, ID and class to look up + $joinClass = $manipulator->getJoinClass(); + $fromRelationName = $manipulator->getForeignKey(); + $toRelationName = $manipulator->getLocalKey(); + + // Create a list of the MMTL relations + $sortlist = DataList::create($joinClass)->filter([ + $toRelationName => $list->column('ID'), + // first() is safe as there are earlier checks to ensure our list to sort is valid + $fromRelationName => $list->first()->getJoin()->$fromRelationName, + ]); + + return $sortlist->map($toRelationName, $sortField)->toArray(); + } } diff --git a/templates/Symbiote/GridFieldExtensions/GridFieldConfigurablePaginator.ss b/templates/Symbiote/GridFieldExtensions/GridFieldConfigurablePaginator.ss index 488c40f..17e3de0 100644 --- a/templates/Symbiote/GridFieldExtensions/GridFieldConfigurablePaginator.ss +++ b/templates/Symbiote/GridFieldExtensions/GridFieldConfigurablePaginator.ss @@ -2,7 +2,7 @@ <%t Symbiote\\GridFieldExtensions\\GridFieldConfigurablePaginator.SHOW 'Show' is 'Verb. Example: Show 1 of 2' %> - <% loop $PageSizes %> <% end_loop %> @@ -14,7 +14,7 @@ $FirstPage $PreviousPage <%t SilverStripe\\Forms\\GridField\\GridFieldPaginator.Page 'Page' %> - + <%t SilverStripe\\Forms\\GridField\\GridFieldPaginator.OF 'of' is 'Example: View 1 of 2' %> $NumPages diff --git a/tests/GridFieldOrderableRowsTest.php b/tests/GridFieldOrderableRowsTest.php index 2f1a54e..dd71b07 100644 --- a/tests/GridFieldOrderableRowsTest.php +++ b/tests/GridFieldOrderableRowsTest.php @@ -9,8 +9,10 @@ use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor; use Symbiote\GridFieldExtensions\GridFieldOrderableRows; use Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild; use Symbiote\GridFieldExtensions\Tests\Stub\StubOrdered; +use Symbiote\GridFieldExtensions\Tests\Stub\StubOrderedVersioned; use Symbiote\GridFieldExtensions\Tests\Stub\StubParent; use Symbiote\GridFieldExtensions\Tests\Stub\StubSubclass; +use Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned; use Symbiote\GridFieldExtensions\Tests\Stub\StubUnorderable; use Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner; use Symbiote\GridFieldExtensions\Tests\Stub\ThroughIntermediary; @@ -21,9 +23,6 @@ use Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs; */ class GridFieldOrderableRowsTest extends SapphireTest { - /** - * @var string - */ protected static $fixture_file = [ 'GridFieldOrderableRowsTest.yml', 'OrderableRowsThroughTest.yml' @@ -89,6 +88,40 @@ class GridFieldOrderableRowsTest extends SapphireTest $this->assertEquals($desiredOrder, $newOrder); } + public function testManyManyThroughListSortOrdersAreUsedForInitialRender() + { + /** @var ThroughDefiner $record */ + $record = $this->objFromFixture(ThroughDefiner::class, 'DefinerOne'); + + $orderable = new GridFieldOrderableRows('Sort'); + $config = new GridFieldConfig_RelationEditor(); + $config->addComponent($orderable); + + $grid = new GridField( + 'Belongings', + 'Testing Many Many', + $record->Belongings()->sort('Sort'), + $config + ); + + // Get the first record, which would be the first one to have column contents generated + /** @var ThroughIntermediary $expected */ + $intermediary = $this->objFromFixture(ThroughIntermediary::class, 'One'); + + $result = $orderable->getColumnContent($grid, $record, 'irrelevant'); + + $this->assertContains( + 'Belongings[GridFieldEditableColumns][' . $record->ID . '][Sort]', + $result, + 'The field name is indexed under the record\'s ID' + ); + $this->assertContains( + 'value="' . $intermediary->Sort . '"', + $result, + 'The value comes from the MMTL intermediary Sort value' + ); + } + public function testSortableChildClass() { $orderable = new GridFieldOrderableRows('Sort'); diff --git a/tests/GridFieldOrderableRowsTest.yml b/tests/GridFieldOrderableRowsTest.yml index 20d5c34..8efc5ec 100644 --- a/tests/GridFieldOrderableRowsTest.yml +++ b/tests/GridFieldOrderableRowsTest.yml @@ -21,7 +21,26 @@ Symbiote\GridFieldExtensions\Tests\Stub\StubOrdered: item6: Sort: 6 nestedtest: - Children: =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item1,=>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item2,=>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item3,=>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item4 + Children: + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item1 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item2 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item3 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item4 + +Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned: + item1: + ExtraField: 1 + Sort: 1 + item2: + ExtraField: 2 + Sort: 2 + item3: + ExtraField: 3 + Sort: 3 + item4: + ExtraField: 4 + Sort: 4 + Symbiote\GridFieldExtensions\Tests\Stub\StubParent: parent: MyManyMany: @@ -37,3 +56,9 @@ Symbiote\GridFieldExtensions\Tests\Stub\StubParent: ManyManySort: 108 - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrdered.item6: ManyManySort: 108 + parent-subclass-ordered-versioned: + MyHasManySubclassOrderedVersioned: + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned.item1 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned.item2 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned.item3 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned.item4 diff --git a/tests/OrderableRowsThroughVersionedTest.php b/tests/OrderableRowsThroughVersionedTest.php index 694da01..e7e3cb1 100644 --- a/tests/OrderableRowsThroughVersionedTest.php +++ b/tests/OrderableRowsThroughVersionedTest.php @@ -12,7 +12,7 @@ use Symbiote\GridFieldExtensions\Tests\Stub\ThroughIntermediary; use Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs; use Symbiote\GridFieldExtensions\GridFieldOrderableRows; -class OrderableRowsThroughTest extends SapphireTest +class OrderableRowsThroughVersionedTest extends SapphireTest { protected static $fixture_file = 'OrderableRowsThroughTest.yml'; @@ -105,7 +105,7 @@ class OrderableRowsThroughTest extends SapphireTest } } $this->assertTrue($differenceFound, 'All records should have changes in draft'); - + // Verify live stage has NOT reordered Versioned::set_stage(Versioned::LIVE); $sameOrder = $parent->$relationName()->sort($sortName)->column('ID'); diff --git a/tests/Stub/StubOrderedVersioned.php b/tests/Stub/StubOrderedVersioned.php new file mode 100644 index 0000000..83bc009 --- /dev/null +++ b/tests/Stub/StubOrderedVersioned.php @@ -0,0 +1,33 @@ + 'Int', + ]; +} diff --git a/tests/Stub/StubSubclassOrderedVersioned.php b/tests/Stub/StubSubclassOrderedVersioned.php new file mode 100644 index 0000000..e0e112d --- /dev/null +++ b/tests/Stub/StubSubclassOrderedVersioned.php @@ -0,0 +1,33 @@ + 'Int', + ]; + + /** + * @var array + */ + private static $has_one = [ + 'Parent' => StubParent::class, + ]; +} diff --git a/tests/Stub/ThroughIntermediary.php b/tests/Stub/ThroughIntermediary.php index 12a48e1..9477d61 100644 --- a/tests/Stub/ThroughIntermediary.php +++ b/tests/Stub/ThroughIntermediary.php @@ -2,18 +2,17 @@ namespace Symbiote\GridFieldExtensions\Tests\Stub; -use SilverStripe\ORM\DataObject; -use SilverStripe\ORM\FieldType\DBInt; use SilverStripe\Dev\TestOnly; +use SilverStripe\ORM\DataObject; class ThroughIntermediary extends DataObject implements TestOnly { private static $table_name = 'IntermediaryThrough'; private static $db = [ - 'Sort' => DBInt::class, + 'Sort' => 'Int', ]; - + private static $has_one = [ 'Defining' => ThroughDefiner::class, 'Belonging' => ThroughBelongs::class,