diff --git a/css/GridFieldPaginator.css b/css/GridFieldPaginator.css new file mode 100644 index 000000000..b11b1037a --- /dev/null +++ b/css/GridFieldPaginator.css @@ -0,0 +1,3 @@ +.ss-gridfield-pagination { text-align: center; padding-bottom: 10px; } + +.ss-gridfield-pagination-button.loading { background: url(../images/network-save.gif) no-repeat 0% 50%; padding-left: 20px; } diff --git a/forms/GridField.php b/forms/GridField.php index c000137ef..43141bf66 100644 --- a/forms/GridField.php +++ b/forms/GridField.php @@ -1,7 +1,25 @@ + * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page')); + * + * + * If you want to modify the output of the grid you can attach a customised + * DataGridPresenter that are the actual Renderer of the data. Sapphire provides + * a default one if you chooses not to. + * + * @see GridFieldPresenter + * @see SS_List + * * @package sapphire * @subpackage forms */ @@ -10,7 +28,7 @@ class GridField extends FormField { /** * @var SS_List */ - protected $dataSource = null; + protected $list = null; /** * @var string @@ -26,26 +44,43 @@ class GridField extends FormField { * @var string - the classname of the DataObject that the GridField will display */ protected $modelClassName = ''; - + + /** + * Url handlers + * + * @var array + */ + public static $url_handlers = array( + '$Action' => '$Action', + ); + /** * Creates a new GridField field * * @param string $name * @param string $title - * @param SS_List $datasource + * @param SS_List $dataList * @param Form $form - * @param string $dataPresenterClassName + * @param string|GridFieldPresenter $dataPresenterClassName - can either pass in a string or an instance of a GridFieldPresenter */ - public function __construct($name, $title = null, SS_List $datasource = null, Form $form = null, $dataPresenterClassName = 'GridFieldPresenter') { + public function __construct($name, $title = null, SS_List $dataList = null, Form $form = null, $dataPresenterClassName = 'GridFieldPresenter') { parent::__construct($name, $title, null, $form); - if ($datasource) { - $this->setDatasource($datasource); + if ($dataList) { + $this->setList($dataList); } $this->setPresenter($dataPresenterClassName); } + /** + * + * @return string - HTML + */ + public function index() { + return $this->FieldHolder(); + } + /** * @param string $modelClassName */ @@ -63,8 +98,8 @@ class GridField extends FormField { if ($this->modelClassName) { return $this->modelClassName; } - if ($this->datasource->dataClass) { - return $this->datasource->dataClass; + if ($this->list->dataClass) { + return $this->list->dataClass; } throw new Exception(get_class($this).' does not have a modelClassName'); @@ -113,11 +148,10 @@ class GridField extends FormField { /** * Set the datasource * - * @param SS_List $datasource + * @param SS_List $list */ - public function setDataSource(SS_List $datasource) { - $this->datasource = $datasource; - + public function setList(SS_List $list) { + $this->list = $list; return $this; } @@ -126,8 +160,8 @@ class GridField extends FormField { * * @return SS_List */ - public function getDataSource() { - return $this->datasource; + public function getList() { + return $this->list; } /** diff --git a/forms/GridFieldPaginator.php b/forms/GridFieldPaginator.php new file mode 100644 index 000000000..e847a2da4 --- /dev/null +++ b/forms/GridFieldPaginator.php @@ -0,0 +1,277 @@ +totalNumberOfPages = $totalNumberOfPages; + $this->currentPage = $currentPage; + } + + /** + * Returns the rendered template for GridField + * + * @return string + */ + public function Render() { + return $this->renderWith(array($this->template)); + } + + /** + * Returns a url to the last page in the result + * + * @return string + */ + public function FirstLink() { + if($this->haveNoPages()) { + return false; + } + return 1; + } + + /** + * Returns a url to the previous page in the result + * + * @return string + */ + public function PreviousLink() { + if($this->isFirstPage() || $this->haveNoPages()) { + return false; + } + // Out of bounds + if($this->currentPage>$this->totalNumberOfPages){ + return $this->LastLink(); + } + + return ($this->currentPage-1); + } + + /** + * Returns a list of pages with links, pagenumber and if it is the current + * page. + * + * @return ArrayList + */ + public function Pages() { + if($this->haveNoPages()) { + return false; + } + + $list = new ArrayList(); + for($idx=1;$idx<=$this->totalNumberOfPages;$idx++) { + $data = new ArrayData(array()); + $data->setField('PageNumber',$idx); + if($idx == $this->currentPage ) { + $data->setField('Current',true); + } else { + $data->setField('Current',false); + } + + $data->setField('Link',$idx); + $list->push($data); + } + return $list; + } + + /** + * Returns a url to the next page in the result + * + * @return string + */ + public function NextLink() { + if($this->isLastPage() || $this->haveNoPages() ) { + return false; + } + // Out of bounds + if($this->currentPage<1) { + return $this->FirstLink(); + } + return ($this->currentPage+1); + } + + /** + * Returns a url to the last page in the result + * + * @return string + */ + public function LastLink() { + if($this->haveNoPages()) { + return false; + } + return ($this->totalNumberOfPages); + } + + /** + * Are we currently on the first page + * + * @return bool + */ + protected function isFirstPage() { + return (bool)($this->currentPage<=1); + } + + /** + * Are we currently on the last page? + * + * @return bool + */ + protected function isLastPage() { + return (bool)($this->currentPage>=$this->totalNumberOfPages); + } + + /** + * Is there only one page of results? + * + * @return bool + */ + protected function haveNoPages() { + return (bool)($this->totalNumberOfPages<=1); + } + +} + +/** + * This is the extension that decorates the GridFieldPresenter. Since a extension + * can't be a Viewable data it's split like this. + * + * @see GridField + * @package sapphire + */ +class GridFieldPaginator_Extension extends Extension { + + /** + * + * @var int + */ + protected $paginationLimit; + + /** + * + * @var int + */ + protected $totalNumberOfPages = 1; + + /** + * + * @var int + */ + protected $currentPage = 1; + + /** + * + * @return string + */ + public function Footer() { + return new GridFieldPaginator($this->totalNumberOfPages, $this->currentPage); + } + + /** + * NOP + */ + public function __construct() {} + + /** + * Set the limit for each page + * + * @param int $limit + * @return GridFieldPaginator_Extension + */ + public function paginationLimit($limit) { + $this->paginationLimit = $limit; + return $this; + } + + /** + * Filter the list to only contain a pagelength of items + * + * @return bool - if the pagination was activated + * @see GridFieldPresenter::Items() + */ + public function filterList(SS_List $list, $parameters){ + if(!$this->canUsePagination($list)) { + return false; + } + + $currentPage = $parameters->Request->requestVar('page'); + if(!$currentPage) { + $currentPage = 1; + } + + $this->totalNumberOfPages = $this->getMaxPagesCount($list); + + if($currentPage<1) { + // Current page is below 1, show nothing and save cpu cycles + $list->where('1=0'); + } elseif($currentPage > $this->totalNumberOfPages) { + // current page is over max pages, show nothing and save cpu cycles + $list->where('1=0'); + } else { + $offset = ($currentPage-1)*$this->paginationLimit; + $list->getRange((int)$offset,$this->paginationLimit); + } + $this->currentPage = $currentPage; + + return true; + } + + /** + * Helper function that see if the pagination has been set and that the + * $list can use pagination. + * + * @param SS_List $list + * @return bool + */ + protected function canUsePagination(SS_List $list) { + if(!$this->paginationLimit) { + return false; + } + if(!method_exists($list, 'getRange')) { + return false; + } + if(!method_exists($list, 'limit')){ + return false; + } + return true; + } + + /** + * + * @return int + */ + protected function getMaxPagesCount($list) { + $list->limit(null); + $number = $list->count(); + $number = ceil($number/$this->paginationLimit); + return $number; + } +} \ No newline at end of file diff --git a/forms/GridFieldPresenter.php b/forms/GridFieldPresenter.php index 306ba4098..27144d6d9 100644 --- a/forms/GridFieldPresenter.php +++ b/forms/GridFieldPresenter.php @@ -1,7 +1,46 @@ + * $presenter = new GridFieldPresenter(); + * $presenter->sort('Title', 'desc'); + * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'),null, $presenter); + * + * + * Another example is to change the template for the rendering + * + * + * $presenter = new GridFieldPresenter(); + * $presenter->setTemplate('MyNiftyGridTemplate'); + * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'),null, $presenter); + * + * + * There is also a possibility to add extensions to the GridPresenter. An + * example is the DataGridPagination that decorates the GridField with + * pagination. Look in the GridFieldPresenter::Items() and the filterList extend + * and GridFieldPresenter::Footers() + * + * + * GridFieldPresenter::add_extension('GridFieldPaginator_Extension'); + * $presenter = new GridFieldPresenter(); + * // This is actually calling GridFieldPaginator_Extension::paginationLimit() + * $presenter->paginationLimit(3); + * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'),null, $presenter); + * + * * @see GridField + * @see GridFieldPaginator * @package sapphire */ class GridFieldPresenter extends ViewableData { @@ -46,10 +85,19 @@ class GridFieldPresenter extends ViewableData { /** * @param string $template */ - function setTemplate($template){ + public function setTemplate($template){ $this->template = $template; } + /** + * The name of the Field + * + * @return string + */ + public function Name() { + return $this->getGridField()->Name(); + } + /** * @param GridField $GridField */ @@ -64,6 +112,14 @@ class GridFieldPresenter extends ViewableData { return $this->GridField; } + /** + * + * @param type $extension + */ + public static function add_extension($extension) { + parent::add_extension(__CLASS__, $extension); + } + /** * Sort the grid by columns * @@ -82,27 +138,29 @@ class GridFieldPresenter extends ViewableData { * @return ArrayList */ public function Items() { - $items = new ArrayList(); + $items = new ArrayList(); if($this->sorting) { - $this->setSorting($this->sorting); + $this->setSortingOnList($this->sorting); } + //empty for now + $list = $this->getGridField()->getList(); - if($sources = $this->getGridField()->getDataSource()) { + $parameters = new stdClass(); + $parameters->Controller = Controller::curr(); + $parameters->Request = Controller::curr()->getRequest(); + + $this->extend('filterList', $list, $parameters); + + if($list) { + $numberOfRows = $list->count(); $counter = 0; - - foreach($sources as $source) { - if(!$source) { - continue; - } - - $itemPresenter = new $this->itemClass($source, $this); - $itemPresenter->iteratorProperties($counter++, $sources->count()); - + foreach($list as $item) { + $itemPresenter = new $this->itemClass($item, $this); + $itemPresenter->iteratorProperties($counter++, $numberOfRows); $items->push($itemPresenter); } } - return $items; } @@ -122,22 +180,32 @@ class GridFieldPresenter extends ViewableData { * @throws Exception */ public function Headers() { - if(!$this->getDatasource()) { + if(!$this->getList()) { throw new Exception(sprintf( '%s needs an data source to be able to render the form', get_class($this->getGridField()) )); } - - $summaryFields = singleton($this->getModelClass())->summaryFields(); - - return $this->summaryFieldsToList($summaryFields); + return $this->summaryFieldsToList($this->FieldList()); + } + + /** + * + * @return ArrayList + */ + public function Footers() { + $arrayList = new ArrayList(); + $footers = $this->extend('Footer'); + foreach($footers as $footer) { + $arrayList->push($footer); + } + return $arrayList; } /** * @return SS_List */ - protected function getDataSource() { - return $this->getGridField()->getDatasource(); + public function getList() { + return $this->getGridField()->getList(); } /** @@ -150,12 +218,12 @@ class GridFieldPresenter extends ViewableData { /** * Add the combined sorting on the datasource * - * If the sorting isn't set in one go on the datasource, only the latest sort - * will be executed.s + * If the sorting isn't set in the datasource, only the latest sort + * will be executed. * * @param array $sortColumns */ - protected function setSorting(array $sortColumns) { + protected function setSortingOnList(array $sortColumns) { $resultColumns = array(); foreach($sortColumns as $column => $sortOrder) { @@ -163,7 +231,7 @@ class GridFieldPresenter extends ViewableData { } $sort = implode(', ', $resultColumns); - $this->getDataSource()->sort($sort); + $this->getList()->sort($sort); } /** diff --git a/javascript/GridFieldPaginator.js b/javascript/GridFieldPaginator.js new file mode 100644 index 000000000..54b6182ae --- /dev/null +++ b/javascript/GridFieldPaginator.js @@ -0,0 +1,30 @@ +jQuery(function($){ + + var onGridClick = function(){ + var form = $(this).closest("form"); + var gridField = $(this).closest(".ss-gridfield"); + $(this).addClass('loading'); + $.ajax({ + type: "POST", + url: form.attr('action')+'/field/'+gridField.attr('id'), + data: form.serialize()+"&page="+$(this).attr('value'), + success: function(data) { + $(gridField).replaceWith(data); + gridInit(); + }, + error: function() { + alert('There seems like there where some failure when trying to fetch the page, please reload and try again.'); + } + + }); + + return false; + } + + var gridInit = function() { + $('.ss-gridfield-pagination-button').click(onGridClick); + } + + gridInit(); + +}); \ No newline at end of file diff --git a/model/DataQuery.php b/model/DataQuery.php index eb1bfbe46..7d9d98fde 100644 --- a/model/DataQuery.php +++ b/model/DataQuery.php @@ -298,13 +298,9 @@ class DataQuery { * Set the limit of this query */ function limit($limit) { - if($limit) { - $clone = $this; - $clone->query->limit($limit); - return $clone; - } else { - return $this; - } + $clone = $this; + $clone->query->limit($limit); + return $clone; } /** diff --git a/scss/GridFieldPaginator.scss b/scss/GridFieldPaginator.scss new file mode 100644 index 000000000..6ce64810c --- /dev/null +++ b/scss/GridFieldPaginator.scss @@ -0,0 +1,7 @@ +.ss-gridfield-pagination { + text-align: center; + padding-bottom: 10px; +} +.ss-gridfield-pagination-button.loading{ + background: url(../images/network-save.gif) no-repeat 0% 50%; padding-left: 20px; +} \ No newline at end of file diff --git a/templates/GridField.ss b/templates/GridField.ss index ee875c0d4..a6b55f01e 100644 --- a/templates/GridField.ss +++ b/templates/GridField.ss @@ -1,7 +1,7 @@ <% require css(sapphire/thirdparty/jquery-ui-themes/smoothness/jquery-ui.css) %> <% require css(sapphire/css/GridField.css) %> -
+
diff --git a/templates/GridFieldPaginator.ss b/templates/GridFieldPaginator.ss new file mode 100644 index 000000000..ba8e1dc74 --- /dev/null +++ b/templates/GridFieldPaginator.ss @@ -0,0 +1,29 @@ +<% require css(sapphire/css/GridFieldPaginator.css) %> + +<% if Pages %> +
+ <% if FirstLink %> + + <% end_if %> + + <% if PreviousLink %> + + <% end_if %> + + <% control Pages %> + <% if Current %> + $PageNumber + <% else %> + + <% end_if %> + <% end_control%> + + <% if NextLink %> + + <% end_if %> + + <% if LastLink %> + + <% end_if %> +
+<% end_if %> \ No newline at end of file diff --git a/templates/GridFieldPresenter.ss b/templates/GridFieldPresenter.ss index e919d32d6..86c633be5 100644 --- a/templates/GridFieldPresenter.ss +++ b/templates/GridFieldPresenter.ss @@ -1,7 +1,7 @@ <% require css(sapphire/thirdparty/jquery-ui-themes/smoothness/jquery-ui.css) %> <% require css(sapphire/css/GridField.css) %> -
+
@@ -19,7 +19,11 @@ -
-
\ No newline at end of file + + <% control Footers %> + $Render + <% end_control %> + +
diff --git a/templates/Includes/GridField_Item.ss b/templates/Includes/GridField_Item.ss index 882f1caa2..d49120690 100644 --- a/templates/Includes/GridField_Item.ss +++ b/templates/Includes/GridField_Item.ss @@ -1,4 +1,4 @@ - + <% control Fields %> class="ss-gridfield-{$FirstLast}"<% end_if %>>$Value <% end_control %> diff --git a/tests/forms/GridFieldFunctionalTest.php b/tests/forms/GridFieldFunctionalTest.php index 0050b8896..67d2fc0df 100644 --- a/tests/forms/GridFieldFunctionalTest.php +++ b/tests/forms/GridFieldFunctionalTest.php @@ -38,7 +38,7 @@ class GridFieldFunctionalTest_Controller extends Controller { public function index() { $grid = new GridField('testgrid'); $dataSource = DataList::create("GridFieldTest_Person")->sort("Name"); - $grid->setDataSource($dataSource); + $grid->setList($dataSource); $form = new Form($this, 'gridform', new FieldList($grid), new FieldList(new FormAction('rerender', 'rerender'))); return array('Form'=>$form); } diff --git a/tests/forms/GridFieldPaginatorTest.php b/tests/forms/GridFieldPaginatorTest.php new file mode 100644 index 000000000..644095f7d --- /dev/null +++ b/tests/forms/GridFieldPaginatorTest.php @@ -0,0 +1,38 @@ +assertTrue(new GridFieldPaginator(1,1) instanceof GridFieldPaginator, 'Trying to find an instance of GridFieldPaginator'); + $this->assertTrue(new GridFieldPaginator_Extension() instanceof GridFieldPaginator_Extension, 'Trying to find an instance of GridFieldPaginator_Extension'); + } + + public function testFlowThroughGridFieldExtension() { + $list = new DataList('GridFieldTest_Person'); + $t = new GridFieldPaginator_Extension(); + $t->paginationLimit(5); + + $parameters = new stdClass(); + $parameters->Request = new SS_HTTPRequest('GET', '/a/url', array('page'=>1)); + + $t->filterList($list, $parameters); + $this->assertTrue($t->Footer() instanceof GridFieldPaginator); + } +} \ No newline at end of file diff --git a/tests/forms/GridFieldTest.php b/tests/forms/GridFieldTest.php index e45ae97fc..bda9385d1 100644 --- a/tests/forms/GridFieldTest.php +++ b/tests/forms/GridFieldTest.php @@ -27,8 +27,8 @@ class GridFieldTest extends SapphireTest { public function testSetDataSource() { $grid = new GridField('Testgrid'); $source = new ArrayList(); - $grid->setDatasource($source); - $this->assertEquals($source, $grid->getDatasource()); + $grid->setList($source); + $this->assertEquals($source, $grid->getList()); } function testSetEmptyDataPresenter() { @@ -76,7 +76,7 @@ class GridFieldTest extends SapphireTest { */ function testFieldHolder() { $grid = new GridField('Testgrid'); - $grid->setDatasource(new DataList('GridFieldTest_Person')); + $grid->setList(new DataList('GridFieldTest_Person')); $this->assertNotNull($grid->FieldHolder()); } }