ENHANCEMENT Release of DataGridPagination

This class extends the DataGridPresenter with the behaviour and looks of a paginated Datagrid.
This commit is contained in:
Stig Lindqvist 2011-10-27 16:11:26 +13:00
parent cf408d766a
commit 83e90aaafe
14 changed files with 544 additions and 58 deletions

View File

@ -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 File

@ -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,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;
}
/**

View File

@ -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 File

@ -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,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);
}
/**

View File

@ -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();
});

View File

@ -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;
}
/**

View File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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);
}

View File

@ -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 File

@ -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());
}
}