Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

ENHANCEMENT Release of DataGridPagination

This class extends the DataGridPresenter with the behaviour and looks of a paginated Datagrid.
  • Loading branch information...
commit 83e90aaafed72e2d59b3dbf9b5a26a4dca3348c2 1 parent cf408d7
@slindqvist slindqvist authored
View
3  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; }
View
66 forms/GridField.php
@@ -1,7 +1,25 @@
<?php
/**
* Displays a {@link SS_List} in a grid format.
- *
+ *
+ * GridFIeld is a field that takes an SS_List and displays it in an table with rows
+ * and columns. It reminds of the old TableFields but works with SS_List types
+ * and only loads the necessary rows from the list.
+ *
+ * The minimum configuration is to pass in name and title of the field and a
+ * SS_List.
+ *
+ * <code>
+ * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'));
+ * </code>
+ *
+ * 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,27 +44,44 @@ 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
*/
public function setModelClass($modelClassName) {
@@ -63,8 +98,8 @@ public function getModelClass() {
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 @@ public function getPresenter(){
/**
* 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 @@ public function setDataSource(SS_List $datasource) {
*
* @return SS_List
*/
- public function getDataSource() {
- return $this->datasource;
+ public function getList() {
+ return $this->list;
}
/**
View
277 forms/GridFieldPaginator.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * GridFieldPaginator decorates the GridFieldPresenter with the possibility to
+ * paginate the GridField.
+ *
+ * @see GridField
+ * @see GridFieldPresenter
+ * @package sapphire
+ */
+class GridFieldPaginator extends ViewableData {
+
+ /**
+ *
+ * @var string
+ */
+ protected $template = 'GridFieldPaginator';
+
+ /**
+ *
+ * @var int
+ */
+ protected $totalNumberOfPages = 0;
+
+ /**
+ *
+ * @var int
+ */
+ protected $currentPage = 0;
+
+ /**
+ *
+ * @param int $totalNumberOfPages
+ * @param int $currentPage
+ */
+ public function __construct($totalNumberOfPages,$currentPage = 1) {
+ Requirements::javascript('sapphire/javascript/GridFieldPaginator.js');
+ $this->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;
+ }
+}
View
120 forms/GridFieldPresenter.php
@@ -1,7 +1,46 @@
<?php
-
/**
+ * The GridFieldPresenter is responsible for rendering and attach user behaviour
+ * to a GridField.
+ *
+ * You can create a GridFieldPresenter and inject that into a GridField to
+ * customise look and feel of GridField.
+ *
+ * It also have the possibility to let extensions to modify the look and feel of
+ * the GridField if you dont want to make a fully blown GridFieldPresenter.
+ *
+ * In the following example we configure the GridField to sort the DataList in
+ * the GridField by Title. This will override the sorting on the DataList.
+ *
+ * <code>
+ * $presenter = new GridFieldPresenter();
+ * $presenter->sort('Title', 'desc');
+ * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'),null, $presenter);
+ * </code>
+ *
+ * Another example is to change the template for the rendering
+ *
+ * <code>
+ * $presenter = new GridFieldPresenter();
+ * $presenter->setTemplate('MyNiftyGridTemplate');
+ * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'),null, $presenter);
+ * </code>
+ *
+ * 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()
+ *
+ * <code>
+ * 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);
+ * </code>
+ *
* @see GridField
+ * @see GridFieldPaginator
* @package sapphire
*/
class GridFieldPresenter extends ViewableData {
@@ -46,11 +85,20 @@ 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
*/
public function setGridField(GridField $grid){
@@ -65,6 +113,14 @@ public function getGridField(){
}
/**
+ *
+ * @param type $extension
+ */
+ public static function add_extension($extension) {
+ parent::add_extension(__CLASS__, $extension);
+ }
+
+ /**
* Sort the grid by columns
*
* @param string $column
@@ -82,27 +138,29 @@ public function sort($column, $direction = 'asc') {
* @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();
+
+ $parameters = new stdClass();
+ $parameters->Controller = Controller::curr();
+ $parameters->Request = Controller::curr()->getRequest();
- if($sources = $this->getGridField()->getDataSource()) {
+ $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 @@ public function Items() {
* @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 @@ protected function getModelClass() {
/**
* 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 @@ protected function setSorting(array $sortColumns) {
}
$sort = implode(', ', $resultColumns);
- $this->getDataSource()->sort($sort);
+ $this->getList()->sort($sort);
}
/**
View
30 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.');
@chillu Owner
chillu added a note

We're using i18n for all core JavaScript through the _t() function, could you add that and ensure that sapphire/javascript/i18n.js and javascript/lang/en_US.js are loaded with the paginator? :)

Certainly, thanks for reminding an old backend monkey to make proper javascript translations as well.

@chillu Owner
chillu added a note

No worries ;) All documented here: http://doc.silverstripe.org/sapphire/en/topics/i18n

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+
+ });
+
+ return false;
+ }
+
+ var gridInit = function() {
+ $('.ss-gridfield-pagination-button').click(onGridClick);
+ }
+
+ gridInit();
+
+});
View
10 model/DataQuery.php
@@ -298,13 +298,9 @@ function sort($sort) {
* 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;
}
/**
View
7 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;
+}
View
2  templates/GridField.ss
@@ -1,7 +1,7 @@
<% require css(sapphire/thirdparty/jquery-ui-themes/smoothness/jquery-ui.css) %>
<% require css(sapphire/css/GridField.css) %>
-<div class="ss-gridfield ui-state-default">
+<div class="ss-gridfield ui-state-default" id="$Name">
<table>
<thead>
<tr>
View
29 templates/GridFieldPaginator.ss
@@ -0,0 +1,29 @@
+<% require css(sapphire/css/GridFieldPaginator.css) %>
+
+<% if Pages %>
+<div class="ss-gridfield-pagination">
+ <% if FirstLink %>
+ <button class="ss-gridfield-pagination-button" type="submit" name="page" value="$FirstLink">First</button>
+ <% end_if %>
+
+ <% if PreviousLink %>
+ <button class="ss-gridfield-pagination-button" type="submit" name="page" value="$PreviousLink">Previous page</button>
+ <% end_if %>
+
+ <% control Pages %>
+ <% if Current %>
+ $PageNumber
+ <% else %>
+ <button class="ss-gridfield-pagination-button" type="submit" name="page" value="$PageNumber">$PageNumber</button>
+ <% end_if %>
+ <% end_control%>
+
+ <% if NextLink %>
+ <button class="ss-gridfield-pagination-button" type="submit" name="page" value="$NextLink">Next Page</button>
+ <% end_if %>
+
+ <% if LastLink %>
+ <button class="ss-gridfield-pagination-button" type="submit" name="page" value="$LastLink">Last</button>
+ <% end_if %>
+</div>
+<% end_if %>
View
10 templates/GridFieldPresenter.ss
@@ -1,7 +1,7 @@
<% require css(sapphire/thirdparty/jquery-ui-themes/smoothness/jquery-ui.css) %>
<% require css(sapphire/css/GridField.css) %>
-<div class="ss-gridfield ui-state-default">
+<div class="ss-gridfield ui-state-default" id="$Name">
<table>
<thead>
<tr>
@@ -19,7 +19,11 @@
</tbody>
<tfoot>
-
</tfoot>
</table>
-</div>
+
+ <% control Footers %>
+ $Render
+ <% end_control %>
+
+</div>
View
2  templates/Includes/GridField_Item.ss
@@ -1,4 +1,4 @@
-<tr class="ss-gridfield-{$EvenOdd}">
+<tr class="ss-gridfield-{$EvenOdd} $FirstLast">
<% control Fields %>
<td <% if FirstLast %>class="ss-gridfield-{$FirstLast}"<% end_if %>>$Value</td>
<% end_control %>
View
2  tests/forms/GridFieldFunctionalTest.php
@@ -38,7 +38,7 @@ function Link($action = null) {
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);
}
View
38 tests/forms/GridFieldPaginatorTest.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @package sapphire
+ * @subpackage tests
+ */
+class GridFieldPaginatorTest extends SapphireTest {
+
+ /**
+ *
+ * @var string
+ */
+ public static $fixture_file = 'sapphire/tests/forms/GridFieldTest.yml';
+
+ /**
+ *
+ * @var array
+ */
+ protected $extraDataObjects = array(
+ 'GridFieldTest_Person',
+ );
+
+ public function testGetInstance() {
+ $this->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);
+ }
+}
View
6 tests/forms/GridFieldTest.php
@@ -27,8 +27,8 @@ public function testGetInstance() {
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 @@ function testFieldHolderWithoutDataSource() {
*/
function testFieldHolder() {
$grid = new GridField('Testgrid');
- $grid->setDatasource(new DataList('GridFieldTest_Person'));
+ $grid->setList(new DataList('GridFieldTest_Person'));
$this->assertNotNull($grid->FieldHolder());
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.