From a1a402224f51befa9ec7e5d49826b89b4f1e6694 Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Wed, 23 Oct 2013 21:59:06 +0100 Subject: [PATCH] NEW: GridField can sort on relation columns Slightly improved logic Add support for relations more than one 'level' apart Add unit tests Fixing PostgreSQL support Throw exception if attempting to sort on a has_many/many_many relation --- forms/gridfield/GridFieldSortableHeader.php | 76 +++++++++++- .../gridfield/GridFieldSortableHeaderTest.php | 115 ++++++++++++++++++ .../gridfield/GridFieldSortableHeaderTest.yml | 39 ++++++ 3 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 tests/forms/gridfield/GridFieldSortableHeaderTest.php create mode 100644 tests/forms/gridfield/GridFieldSortableHeaderTest.yml diff --git a/forms/gridfield/GridFieldSortableHeader.php b/forms/gridfield/GridFieldSortableHeader.php index 598394089..3f693dfac 100644 --- a/forms/gridfield/GridFieldSortableHeader.php +++ b/forms/gridfield/GridFieldSortableHeader.php @@ -87,6 +87,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM $state = $gridField->State->GridFieldSortableHeader; $columns = $gridField->getColumns(); $currentColumn = 0; + $list = $gridField->getList(); foreach($columns as $columnField) { $currentColumn++; @@ -96,7 +97,31 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM if(isset($this->fieldSorting[$columnField]) && $this->fieldSorting[$columnField]) { $columnField = $this->fieldSorting[$columnField]; } - if($title && $gridField->getList()->canSortBy($columnField)) { + + $allowSort = ($title && $list->canSortBy($columnField)); + + if(strpos($columnField, '.') !== false) { + // we have a relation column with dot notation + $parts = explode('.', $columnField); + + $tmpItem = singleton($list->dataClass()); + for($idx = 0; $idx < sizeof($parts); $idx++) { + $methodName = $parts[$idx]; + if($tmpItem instanceof DataObject && $tmpItem->hasField($methodName)) { + // If we've found a field, we can sort on it + $allowSort = true; + } else if ($tmpItem instanceof SS_List) { + // It's impossible to sort on a HasManyList/ManyManyList + throw new Exception(__CLASS__ . ' is unable to sort on has_many/many_many relations,' + . ' please only specify has_one relations'); + } else { + // Else, the part is a relation name, so get the object/list from it + $tmpItem = $tmpItem->$methodName(); + } + } + } + + if($allowSort) { $dir = 'asc'; if($state->SortColumn == $columnField && $state->SortDirection == 'asc') { $dir = 'desc'; @@ -161,6 +186,14 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM } } + /** + * Returns the manipulated (sorted) DataList. Field names will simply add an 'ORDER BY' + * clause, relation names will add appropriate joins to the DataQuery first. + * + * @param GridField + * @param SS_List + * @return SS_List + */ public function getManipulatedData(GridField $gridField, SS_List $dataList) { if(!$this->checkDataType($dataList)) return $dataList; @@ -168,6 +201,45 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM if ($state->SortColumn == "") { return $dataList; } - return $dataList->sort($state->SortColumn, $state->SortDirection); + + $column = $state->SortColumn; + + // if we have a relation column with dot notation + if(strpos($column, '.') !== false) { + $lastAlias = $dataList->dataClass(); + $tmpItem = singleton($lastAlias); + $parts = explode('.', $state->SortColumn); + + for($idx = 0; $idx < sizeof($parts); $idx++) { + $methodName = $parts[$idx]; + + // If we're not on the last item, we're looking at a relation + if($idx !== sizeof($parts) - 1) { + // Traverse to the relational list + $tmpItem = $tmpItem->$methodName(); + + // Add a left join to the query + $dataList = $dataList->leftJoin( + $tmpItem->class, + '"' . $methodName . '"."ID" = "' . $lastAlias . '"."' . $methodName . 'ID"', + $methodName + ); + + // Store the last 'alias' name as it'll be used for the next + // join, or the 'sort' column + $lastAlias = $methodName; + } else { + // Change relation.relation.fieldname to alias.fieldname + $column = $lastAlias . '.' . $methodName; + } + } + } + + // We need to manually create our ORDER BY "Foo"."Bar" string for relations, + // as ->sort() won't do it by itself. Blame PostgreSQL for making this necessary + $pieces = explode('.', $column); + $column = '"' . implode('"."', $pieces) . '"'; + + return $dataList->sort($column, $state->SortDirection); } } diff --git a/tests/forms/gridfield/GridFieldSortableHeaderTest.php b/tests/forms/gridfield/GridFieldSortableHeaderTest.php new file mode 100644 index 000000000..c7c6052ca --- /dev/null +++ b/tests/forms/gridfield/GridFieldSortableHeaderTest.php @@ -0,0 +1,115 @@ +State->GridFieldSortableHeader; + $state->SortColumn = 'City'; + $state->SortDirection = 'asc'; + + $compontent = $gridField->getConfig()->getComponentByType('GridFieldSortableHeader'); + $listA = $compontent->getManipulatedData($gridField, $list); + + $state->SortDirection = 'desc'; + $listB = $compontent->getManipulatedData($gridField, $list); + + $this->assertEquals( + array('Auckland', 'Cologne', 'Melbourne', 'Wellington'), + $listA->column('City') + ); + $this->assertEquals( + array('Wellington', 'Melbourne', 'Cologne', 'Auckland'), + $listB->column('City') + ); + + // Test one relation 'deep' + $state->SortColumn = 'Cheerleader.Name'; + $state->SortDirection = 'asc'; + $relationListA = $compontent->getManipulatedData($gridField, $list); + + $state->SortDirection = 'desc'; + $relationListB = $compontent->getManipulatedData($gridField, $list); + + $this->assertEquals( + array('Wellington', 'Melbourne', 'Cologne', 'Auckland'), + $relationListA->column('City') + ); + $this->assertEquals( + array('Auckland', 'Cologne', 'Melbourne', 'Wellington'), + $relationListB->column('City') + ); + + // Test two relations 'deep' + $state->SortColumn = 'Cheerleader.Hat.Colour'; + $state->SortDirection = 'asc'; + $relationListC = $compontent->getManipulatedData($gridField, $list); + + $state->SortDirection = 'desc'; + $relationListD = $compontent->getManipulatedData($gridField, $list); + + $this->assertEquals( + array('Cologne', 'Auckland', 'Wellington', 'Melbourne'), + $relationListC->column('City') + ); + $this->assertEquals( + array('Melbourne', 'Wellington', 'Auckland', 'Cologne'), + $relationListD->column('City') + ); + } + +} + +class GridFieldSortableHeaderTest_Team extends DataObject implements TestOnly { + + private static $db = array( + 'Name' => 'Varchar', + 'City' => 'Varchar' + ); + + private static $has_one = array( + 'Cheerleader' => 'GridFieldSortableHeaderTest_Cheerleader' + ); + +} + +class GridFieldSortableHeaderTest_Cheerleader extends DataObject implements TestOnly { + + private static $db = array( + 'Name' => 'Varchar' + ); + + private static $has_one = array( + 'Team' => 'GridFieldSortableHeaderTest_Team', + 'Hat' => 'GridFieldSortableHeaderTest_CheerleaderHat' + ); + +} + +class GridFieldSortableHeaderTest_CheerleaderHat extends DataObject implements TestOnly { + + private static $db = array( + 'Colour' => 'Varchar' + ); + + private static $has_one = array( + 'Cheerleader' => 'GridFieldSortableHeaderTest_Cheerleader' + ); + +} \ No newline at end of file diff --git a/tests/forms/gridfield/GridFieldSortableHeaderTest.yml b/tests/forms/gridfield/GridFieldSortableHeaderTest.yml new file mode 100644 index 000000000..ada03d050 --- /dev/null +++ b/tests/forms/gridfield/GridFieldSortableHeaderTest.yml @@ -0,0 +1,39 @@ +GridFieldSortableHeaderTest_CheerleaderHat: + hat1: + Colour: Blue + hat2: + Colour: Red + hat3: + Colour: Green + hat4: + Colour: Pink +GridFieldSortableHeaderTest_Cheerleader: + cheerleader1: + Name: Heather + Hat: =>GridFieldSortableHeaderTest_CheerleaderHat.hat2 + cheerleader2: + Name: Bob + Hat: =>GridFieldSortableHeaderTest_CheerleaderHat.hat4 + cheerleader3: + Name: Jenny + Hat: =>GridFieldSortableHeaderTest_CheerleaderHat.hat1 + cheerleader4: + Name: Sam + Hat: =>GridFieldSortableHeaderTest_CheerleaderHat.hat3 +GridFieldSortableHeaderTest_Team: + team1: + Name: Team 1 + City: Cologne + Cheerleader: =>GridFieldSortableHeaderTest_Cheerleader.cheerleader3 + team2: + Name: Team 2 + City: Wellington + Cheerleader: =>GridFieldSortableHeaderTest_Cheerleader.cheerleader2 + team3: + Name: Team 3 + City: Auckland + Cheerleader: =>GridFieldSortableHeaderTest_Cheerleader.cheerleader4 + team4: + Name: Team 4 + City: Melbourne + Cheerleader: =>GridFieldSortableHeaderTest_Cheerleader.cheerleader1