Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
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.