mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #153 from silverstripe-scienceninjas/pull/ssf-94
GridField for CMSAssetgis
This commit is contained in:
commit
5e8b50257a
@ -1,17 +1,33 @@
|
|||||||
/** Core styles for the basic GridField form field without any specific style. @package sapphire @subpackage scss */
|
/** Core styles for the basic GridField form field without any specific style. @package sapphire @subpackage scss @todo Add radial gradient to default delete button state @todo Create SASS mixin-function to simply swap the from/to, to to/from colours in grsdient mixins? */
|
||||||
.ss-gridfield { border: none; }
|
.cms table.ss-gridfield { width: 100%; padding: 0; margin: 20px 0 0 0; border-collapse: separate; display: table; border-bottom: 0 none; }
|
||||||
.ss-gridfield table { width: 100%; border-collapse: collapse; border-spacing: 0; background: #fff; border: 1px solid #c1c1c1; }
|
.cms table.ss-gridfield thead { color: #1d2224; background: transparent; }
|
||||||
.ss-gridfield thead { color: #5a5a5a; background: #dadada; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f3f3f3), color-stop(100%, #dadada)); background-image: -webkit-linear-gradient(#f3f3f3, #dadada); background-image: -moz-linear-gradient(#f3f3f3, #dadada); background-image: -o-linear-gradient(#f3f3f3, #dadada); background-image: -ms-linear-gradient(#f3f3f3, #dadada); background-image: linear-gradient(#f3f3f3, #dadada); }
|
.cms table.ss-gridfield tbody { background: #FFF; }
|
||||||
.ss-gridfield thead th { font-weight: bold; padding: 8px 24px 8px 8px; position: relative; border: 1px solid #c1c1c1; border-width: 0 1px 1px 0; }
|
.cms table.ss-gridfield tbody td { /* Rounded buttons */ }
|
||||||
.ss-gridfield thead th.ss-gridfield-sortable.hover { color: #747474; cursor: pointer; background: #f3f3f3; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e7e7e7)); background-image: -webkit-linear-gradient(#ffffff, #e7e7e7); background-image: -moz-linear-gradient(#ffffff, #e7e7e7); background-image: -o-linear-gradient(#ffffff, #e7e7e7); background-image: -ms-linear-gradient(#ffffff, #e7e7e7); background-image: linear-gradient(#ffffff, #e7e7e7); }
|
.cms table.ss-gridfield tbody td button { border: #CC0033 solid 1px; background: #CC0033; color: #FFF; -moz-border-radius: 15px; -webkit-border-radius: 15px; -o-border-radius: 15px; -ms-border-radius: 15px; -khtml-border-radius: 15px; border-radius: 15px; margin: 0 0 0 2px; padding: 0; width: auto; min-width: 30px; height: 30px; text-shadow: none; }
|
||||||
.ss-gridfield thead th.ss-gridfield-sorted { background: #e7e7e7; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dadada), color-stop(100%, #f3f3f3)); background-image: -webkit-linear-gradient(#dadada, #f3f3f3); background-image: -moz-linear-gradient(#dadada, #f3f3f3); background-image: -o-linear-gradient(#dadada, #f3f3f3); background-image: -ms-linear-gradient(#dadada, #f3f3f3); background-image: linear-gradient(#dadada, #f3f3f3); }
|
.cms table.ss-gridfield tbody td button:hover { color: #222; }
|
||||||
.ss-gridfield thead th.ss-gridfield-sorted.hover { background: #f3f3f3; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #e7e7e7), color-stop(100%, #f9f9f9)); background-image: -webkit-linear-gradient(#e7e7e7, #f9f9f9); background-image: -moz-linear-gradient(#e7e7e7, #f9f9f9); background-image: -o-linear-gradient(#e7e7e7, #f9f9f9); background-image: -ms-linear-gradient(#e7e7e7, #f9f9f9); background-image: linear-gradient(#e7e7e7, #f9f9f9); }
|
.cms table.ss-gridfield tfoot { color: #1d2224; }
|
||||||
.ss-gridfield thead th .ui-icon { position: absolute; top: 5px; right: 0; }
|
.cms table.ss-gridfield tfoot tr td { background: #95a5ab; padding: .7em; }
|
||||||
.ss-gridfield thead th.ss-gridfield-desc .ui-icon { background-position: 0 -48px; }
|
.cms table.ss-gridfield tr.sortable-header th { background: #7f9198; }
|
||||||
.ss-gridfield thead th.ss-gridfield-asc .ui-icon { background-position: -64px -48px; }
|
.cms table.ss-gridfield tr:hover { background: #FFFAD6 !important; }
|
||||||
.ss-gridfield td { padding: 8px; border-right: 1px solid #f3f3f3; }
|
.cms table.ss-gridfield tr:first-child { background: transparent; }
|
||||||
.ss-gridfield td.ss-gridfield-last { border-right: none; }
|
.cms table.ss-gridfield tr.ss-gridfield-even { background: #f2f9fd; }
|
||||||
.ss-gridfield tr.ss-gridfield-even { border: 1px solid #c6e5f6; border-width: 1px 0; background: #f2f9fd; }
|
.cms table.ss-gridfield tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; }
|
||||||
.ss-gridfield tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; }
|
.cms table.ss-gridfield tr th { font-weight: bold; font-size: 12px; color: #FFF; padding: 0; border-right: 1px solid #85959C; height: 20px; /* Makes it appear as though the text sits over the boundary of the two <tr>'s in <thead> */ }
|
||||||
.ss-gridfield tr.ss-gridfield-even td { border-right: 1px solid #dceffa; }
|
.cms table.ss-gridfield tr th span { display: block; position: relative; left: 20px; top: -7px; width: 100%; }
|
||||||
.ss-gridfield tr.ss-gridfield-even td.ss-gridfield-last { border-right: none; }
|
.cms table.ss-gridfield tr th div.fieldgroup, .cms table.ss-gridfield tr th div.fieldgroup-field { width: auto; }
|
||||||
|
.cms table.ss-gridfield tr th div.fieldgroup { min-width: 200px; /* Not sure why IE think it needs this */ }
|
||||||
|
.cms table.ss-gridfield tr th.extra, .cms table.ss-gridfield tr th.action { background: #7f9198; padding: 0; cursor: default; }
|
||||||
|
.cms table.ss-gridfield tr th.extra button.ss-ui-button, .cms table.ss-gridfield tr th.extra button:hover.ss-ui-button, .cms table.ss-gridfield tr th.action button.ss-ui-button, .cms table.ss-gridfield tr th.action button:hover.ss-ui-button { margin-left: .9em; color: #222; }
|
||||||
|
.cms table.ss-gridfield tr th.extra { text-align: center; background: #b1c0c5; background: -moz-linear-gradient(#b1c0c5 20%, #7f9198); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #b1c0c5), color-stop(80%, #7f9198)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #b1c0c5 20%, #7f9198 80%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, #b1c0c5 20%, #7f9198 80%); /* Opera 11.10+ */ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#b1c0c5', endColorstr='#7f9198'); /* IE5.5+ */ background: -ms-linear-gradient(top, #b1c0c5 20%, #7f9198 80%); /* IE10+ */ background: linear-gradient(top, #b1c0c5 20%, #7f9198 80%); /* W3C */ }
|
||||||
|
.cms table.ss-gridfield tr th.extra span { width: auto; display: inline; position: static; }
|
||||||
|
.cms table.ss-gridfield tr th.action { border-right: 0; }
|
||||||
|
.cms table.ss-gridfield tr th.first { -moz-border-radius-topleft: 7px; -webkit-border-top-left-radius: 7px; -o-border-top-left-radius: 7px; -ms-border-top-left-radius: 7px; -khtml-border-top-left-radius: 7px; border-top-left-radius: 7px; }
|
||||||
|
.cms table.ss-gridfield tr th.last { -moz-border-radius-topright: 7px; -webkit-border-top-right-radius: 7px; -o-border-top-right-radius: 7px; -ms-border-top-right-radius: 7px; -khtml-border-top-right-radius: 7px; border-top-right-radius: 7px; }
|
||||||
|
.cms table.ss-gridfield tr th button, .cms table.ss-gridfield tr th button:hover { font-size: 12px; margin-left: -0.9em; }
|
||||||
|
.cms table.ss-gridfield tr th button.ss-gridfield-sort, .cms table.ss-gridfield tr th button:hover.ss-gridfield-sort { text-align: left; padding: 0; color: #FFF; width: 95%; background: transparent; border: 0 none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; text-shadow: none; }
|
||||||
|
.cms table.ss-gridfield tr th button:hover { color: #CCC !important; /* Not sure why IE think it needs this */ }
|
||||||
|
.cms table.ss-gridfield tr th.extra button.ss-ui-button { padding: .3em; line-height: 1; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; position: relative; top: -24px; border: #b1c0c5 solid 10px; border-bottom-width: 0; }
|
||||||
|
.cms table.ss-gridfield tr th input.ss-gridfield-sort { position: relative; top: -24px; padding: 2px; width: 65%; margin: 0 auto; border: #b1c0c5 solid 10px; border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
||||||
|
.cms table.ss-gridfield tr th input.ss-gridfield-sort:focus { -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }
|
||||||
|
.cms table.ss-gridfield tr td { border-right: 1px solid #dbdddd; padding: 10px; }
|
||||||
|
.cms table.ss-gridfield tr td.bottom-all { -moz-border-radius-bottomleft: 7px; -webkit-border-bottom-left-radius: 7px; -o-border-bottom-left-radius: 7px; -ms-border-bottom-left-radius: 7px; -khtml-border-bottom-left-radius: 7px; border-bottom-left-radius: 7px; -moz-border-radius-bottomright: 7px; -webkit-border-bottom-right-radius: 7px; -o-border-bottom-right-radius: 7px; -ms-border-bottom-right-radius: 7px; -khtml-border-bottom-right-radius: 7px; border-bottom-right-radius: 7px; background: #7f9198; background: -moz-linear-gradient(#7f9198 20%, #b1c0c5); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #7f9198), color-stop(80%, #b1c0c5)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #7f9198 20%, #b1c0c5 80%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, #7f9198 20%, #b1c0c5 80%); /* Opera 11.10+ */ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#7f9198', endColorstr='#b1c0c5'); /* IE5.5+ */ background: -ms-linear-gradient(top, #7f9198 20%, #7f9198 80%); /* IE10+ */ background: linear-gradient(top, #7f9198 20%, #b1c0c5 80%); /* W3C */ }
|
||||||
|
@ -25,12 +25,26 @@ class Folder extends File {
|
|||||||
|
|
||||||
static $default_sort = "\"Sort\"";
|
static $default_sort = "\"Sort\"";
|
||||||
|
|
||||||
function populateDefaults() {
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function populateDefaults() {
|
||||||
parent::populateDefaults();
|
parent::populateDefaults();
|
||||||
|
|
||||||
if(!$this->Name) $this->Name = _t('AssetAdmin.NEWFOLDER',"NewFolder");
|
if(!$this->Name) $this->Name = _t('AssetAdmin.NEWFOLDER',"NewFolder");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $folderPath string Absolute or relative path to the file.
|
||||||
|
* If path is relative, its interpreted relative to the "assets/" directory.
|
||||||
|
* @return Folder
|
||||||
|
* @deprecated in favor of the correct name find_or_make
|
||||||
|
*/
|
||||||
|
public static function findOrMake($folderPath) {
|
||||||
|
Deprecation::notice('3.0', "Folder::findOrMake() is deprecated in favor of Folder::find_or_make()");
|
||||||
|
return self::find_or_make($folderPath);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the given folder or create it both as {@link Folder} database records
|
* Find the given folder or create it both as {@link Folder} database records
|
||||||
* and on the filesystem. If necessary, creates parent folders as well.
|
* and on the filesystem. If necessary, creates parent folders as well.
|
||||||
@ -39,7 +53,7 @@ class Folder extends File {
|
|||||||
* If path is relative, its interpreted relative to the "assets/" directory.
|
* If path is relative, its interpreted relative to the "assets/" directory.
|
||||||
* @return Folder
|
* @return Folder
|
||||||
*/
|
*/
|
||||||
static function findOrMake($folderPath) {
|
public static function find_or_make($folderPath) {
|
||||||
// Create assets directory, if it is missing
|
// Create assets directory, if it is missing
|
||||||
if(!file_exists(ASSETS_PATH)) Filesystem::makeFolder(ASSETS_PATH);
|
if(!file_exists(ASSETS_PATH)) Filesystem::makeFolder(ASSETS_PATH);
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ class Upload extends Controller {
|
|||||||
// @TODO This puts a HUGE limitation on files especially when lots
|
// @TODO This puts a HUGE limitation on files especially when lots
|
||||||
// have been uploaded.
|
// have been uploaded.
|
||||||
$base = Director::baseFolder();
|
$base = Director::baseFolder();
|
||||||
$parentFolder = Folder::findOrMake($folderPath);
|
$parentFolder = Folder::find_or_make($folderPath);
|
||||||
|
|
||||||
// Create a folder for uploading.
|
// Create a folder for uploading.
|
||||||
if(!file_exists(ASSETS_PATH)){
|
if(!file_exists(ASSETS_PATH)){
|
||||||
|
@ -302,6 +302,23 @@ class Form extends RequestHandler {
|
|||||||
sprintf('Action "%s" not allowed on form (Name: "%s")', $funcName, $this->Name())
|
sprintf('Action "%s" not allowed on form (Name: "%s")', $funcName, $this->Name())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// TODO : Once we switch to a stricter policy regarding allowed_actions (meaning actions must be set explicitly in allowed_actions in order to run)
|
||||||
|
// Uncomment the following for checking security against running actions on form fields
|
||||||
|
/* else {
|
||||||
|
// Try to find a field that has the action, and allows it
|
||||||
|
$fieldsHaveMethod = false;
|
||||||
|
foreach ($this->Fields() as $field){
|
||||||
|
if ($field->hasMethod($funcName) && $field->checkAccessAction($funcName)) {
|
||||||
|
$fieldsHaveMethod = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$fieldsHaveMethod) {
|
||||||
|
return $this->httpError(
|
||||||
|
403,
|
||||||
|
sprintf('Action "%s" not allowed on any fields of form (Name: "%s")', $funcName, $this->Name())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
// Validate the form
|
// Validate the form
|
||||||
if(!$this->validate()) {
|
if(!$this->validate()) {
|
||||||
@ -344,6 +361,12 @@ class Form extends RequestHandler {
|
|||||||
// Otherwise, try a handler method on the form object.
|
// Otherwise, try a handler method on the form object.
|
||||||
} elseif($this->hasMethod($funcName)) {
|
} elseif($this->hasMethod($funcName)) {
|
||||||
return $this->$funcName($vars, $this, $request);
|
return $this->$funcName($vars, $this, $request);
|
||||||
|
} else {
|
||||||
|
// Finally try to find a field that could handle that action, ie GridField
|
||||||
|
foreach ($this->Fields() as $field){
|
||||||
|
if (!$field->hasMethod($funcName)) continue;
|
||||||
|
return $field->$funcName($vars, $this, $request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->httpError(404);
|
return $this->httpError(404);
|
||||||
|
@ -1,174 +0,0 @@
|
|||||||
<?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
|
|
||||||
*/
|
|
||||||
class GridField extends FormField {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var SS_List
|
|
||||||
*/
|
|
||||||
protected $list = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $presenterClassName = "GridFieldPresenter";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var GridFieldPresenter
|
|
||||||
*/
|
|
||||||
protected $presenter = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 $dataList
|
|
||||||
* @param Form $form
|
|
||||||
* @param string|GridFieldPresenter $dataPresenterClassName - can either pass in a string or an instance of a GridFieldPresenter
|
|
||||||
*/
|
|
||||||
public function __construct($name, $title = null, SS_List $dataList = null, Form $form = null, $dataPresenterClassName = 'GridFieldPresenter') {
|
|
||||||
parent::__construct($name, $title, null, $form);
|
|
||||||
|
|
||||||
if ($dataList) {
|
|
||||||
$this->setList($dataList);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setPresenter($dataPresenterClassName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return string - HTML
|
|
||||||
*/
|
|
||||||
public function index() {
|
|
||||||
return $this->FieldHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $modelClassName
|
|
||||||
*/
|
|
||||||
public function setModelClass($modelClassName) {
|
|
||||||
$this->modelClassName = $modelClassName;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getModelClass() {
|
|
||||||
if ($this->modelClassName) {
|
|
||||||
return $this->modelClassName;
|
|
||||||
}
|
|
||||||
if ($this->list->dataClass) {
|
|
||||||
return $this->list->dataClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new LogicException(get_class($this).' does not have a modelClassName');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|GridFieldPresenter
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function setPresenter($presenter) {
|
|
||||||
if(!$presenter){
|
|
||||||
throw new InvalidArgumentException('setPresenter() for GridField must be set with a class');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_object($presenter)) {
|
|
||||||
$this->presenter = $presenter;
|
|
||||||
$this->presenter->setGridField($this);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!class_exists($presenter)){
|
|
||||||
throw new InvalidArgumentException('DataPresenter for GridField must be set with an existing class, '.$presenter.' does not exists.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if($presenter !='GridFieldPresenter' && !is_subclass_of($presenter, 'GridFieldPresenter')) {
|
|
||||||
throw new InvalidArgumentException(sprintf(
|
|
||||||
'DataPresenter "%s" must subclass GridFieldPresenter', $presenter
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->presenter = new $presenter;
|
|
||||||
$this->presenter->setGridField($this);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return GridFieldPresenter
|
|
||||||
*/
|
|
||||||
public function getPresenter(){
|
|
||||||
return $this->presenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the datasource
|
|
||||||
*
|
|
||||||
* @param SS_List $list
|
|
||||||
*/
|
|
||||||
public function setList(SS_List $list) {
|
|
||||||
$this->list = $list;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the datasource
|
|
||||||
*
|
|
||||||
* @return SS_List
|
|
||||||
*/
|
|
||||||
public function getList() {
|
|
||||||
return $this->list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string - html for the form
|
|
||||||
*/
|
|
||||||
function FieldHolder() {
|
|
||||||
return $this->getPresenter()->render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
|||||||
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,417 +0,0 @@
|
|||||||
<?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 {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Template override
|
|
||||||
*
|
|
||||||
* @var string $template
|
|
||||||
*/
|
|
||||||
protected $template = 'GridFieldPresenter';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class name for each item/row
|
|
||||||
*
|
|
||||||
* @var string $itemClass
|
|
||||||
*/
|
|
||||||
protected $itemClass = 'GridFieldPresenter_Item';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var GridField
|
|
||||||
*/
|
|
||||||
protected $GridField = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public $fieldCasting = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public $fieldFormatting = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of columns and direction that the {@link GridFieldPresenter} is
|
|
||||||
* sorted in.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $sorting = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $template
|
|
||||||
*/
|
|
||||||
public function setTemplate($template){
|
|
||||||
$this->template = $template;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the Field
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getName() {
|
|
||||||
return $this->getGridField()->getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param GridField $GridField
|
|
||||||
*/
|
|
||||||
public function setGridField(GridField $grid){
|
|
||||||
$this->GridField = $grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return GridField
|
|
||||||
*/
|
|
||||||
public function getGridField(){
|
|
||||||
return $this->GridField;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param type $extension
|
|
||||||
*/
|
|
||||||
public static function add_extension($extension) {
|
|
||||||
parent::add_extension(__CLASS__, $extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the grid by columns
|
|
||||||
*
|
|
||||||
* @param string $column
|
|
||||||
* @param string $direction
|
|
||||||
*/
|
|
||||||
public function sort($column, $direction = 'asc') {
|
|
||||||
$this->sorting[$column] = $direction;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an {@link ArrayList} of {@link GridField_Item} objects, suitable for display in the template.
|
|
||||||
*
|
|
||||||
* @return ArrayList
|
|
||||||
*/
|
|
||||||
public function Items() {
|
|
||||||
$items = new ArrayList();
|
|
||||||
|
|
||||||
if($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();
|
|
||||||
|
|
||||||
$this->extend('filterList', $list, $parameters);
|
|
||||||
|
|
||||||
if($list) {
|
|
||||||
$numberOfRows = $list->count();
|
|
||||||
$counter = 0;
|
|
||||||
foreach($list as $item) {
|
|
||||||
$itemPresenter = new $this->itemClass($item, $this);
|
|
||||||
$itemPresenter->iteratorProperties($counter++, $numberOfRows);
|
|
||||||
$items->push($itemPresenter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the headers or column names for this grid
|
|
||||||
*
|
|
||||||
* The returning array will have the format of
|
|
||||||
*
|
|
||||||
* <code>
|
|
||||||
* array(
|
|
||||||
* 'FirstName' => 'First name',
|
|
||||||
* 'Description' => 'A nice description'
|
|
||||||
* )
|
|
||||||
* </code>
|
|
||||||
*
|
|
||||||
* @return ArrayList
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function Headers() {
|
|
||||||
if(!$this->getList()) {
|
|
||||||
throw new LogicException(sprintf(
|
|
||||||
'%s needs an data source to be able to render the form', get_class($this->getGridField())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
public function getList() {
|
|
||||||
return $this->getGridField()->getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string - name of model
|
|
||||||
*/
|
|
||||||
protected function getModelClass() {
|
|
||||||
return $this->getGridField()->getModelClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the combined sorting on the datasource
|
|
||||||
*
|
|
||||||
* If the sorting isn't set in the datasource, only the latest sort
|
|
||||||
* will be executed.
|
|
||||||
*
|
|
||||||
* @param array $sortColumns
|
|
||||||
*/
|
|
||||||
protected function setSortingOnList(array $sortColumns) {
|
|
||||||
$resultColumns = array();
|
|
||||||
|
|
||||||
foreach($sortColumns as $column => $sortOrder) {
|
|
||||||
$resultColumns[] = sprintf("%s %s", $column ,$sortOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sort = implode(', ', $resultColumns);
|
|
||||||
$this->getList()->sort($sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function FieldList() {
|
|
||||||
return singleton($this->getModelClass())->summaryFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translate the summaryFields from a model into a format that is understood
|
|
||||||
* by the Form renderer
|
|
||||||
*
|
|
||||||
* @param array $summaryFields
|
|
||||||
*
|
|
||||||
* @return ArrayList
|
|
||||||
*/
|
|
||||||
protected function summaryFieldsToList($summaryFields) {
|
|
||||||
$headers = new ArrayList();
|
|
||||||
|
|
||||||
if(is_array($summaryFields)) {
|
|
||||||
$counter = 0;
|
|
||||||
|
|
||||||
foreach ($summaryFields as $name => $title) {
|
|
||||||
$data = array(
|
|
||||||
'Name' => $name,
|
|
||||||
'Title' => $title,
|
|
||||||
'IsSortable' => true,
|
|
||||||
'IsSorted' => false,
|
|
||||||
'SortedDirection' => 'asc'
|
|
||||||
);
|
|
||||||
|
|
||||||
if(array_key_exists($name, $this->sorting)) {
|
|
||||||
$data['IsSorted'] = true;
|
|
||||||
$data['SortedDirection'] = $this->sorting[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = new ArrayData($data);
|
|
||||||
$result->iteratorProperties($counter++, count($summaryFields));
|
|
||||||
|
|
||||||
$headers->push($result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $casting
|
|
||||||
*/
|
|
||||||
function setFieldCasting($casting) {
|
|
||||||
$this->fieldCasting = $casting;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param type $formatting
|
|
||||||
*/
|
|
||||||
function setFieldFormatting($formatting) {
|
|
||||||
$this->fieldFormatting = $formatting;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string - html
|
|
||||||
*/
|
|
||||||
function render(){
|
|
||||||
return $this->renderWith(array($this->template));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single record in a GridField.
|
|
||||||
*
|
|
||||||
* @package sapphire
|
|
||||||
* @see GridField
|
|
||||||
*/
|
|
||||||
class GridFieldPresenter_Item extends ViewableData {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Object The underlying record, usually an element of
|
|
||||||
* {@link GridField->datasource()}.
|
|
||||||
*/
|
|
||||||
protected $item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var GridFieldPresenter
|
|
||||||
*/
|
|
||||||
protected $parent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Object $item
|
|
||||||
* @param GridFieldPresenter $parent
|
|
||||||
*/
|
|
||||||
public function __construct($item, $parent) {
|
|
||||||
$this->failover = $this->item = $item;
|
|
||||||
$this->parent = $parent;
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function ID() {
|
|
||||||
return $this->item->ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return type
|
|
||||||
*/
|
|
||||||
public function Parent() {
|
|
||||||
return $this->parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param bool $xmlSafe
|
|
||||||
*
|
|
||||||
* @return ArrayList
|
|
||||||
*/
|
|
||||||
public function Fields($xmlSafe = true) {
|
|
||||||
$list = $this->parent->FieldList();
|
|
||||||
$counter = 0;
|
|
||||||
|
|
||||||
foreach($list as $fieldName => $fieldTitle) {
|
|
||||||
$value = "";
|
|
||||||
|
|
||||||
// TODO Delegates that to DataList
|
|
||||||
// This supports simple FieldName syntax
|
|
||||||
if(strpos($fieldName,'.') === false) {
|
|
||||||
$value = ($this->item->XML_val($fieldName) && $xmlSafe) ? $this->item->XML_val($fieldName) : $this->item->RAW_val($fieldName);
|
|
||||||
|
|
||||||
// This support the syntax fieldName = Relation.RelatedField
|
|
||||||
} else {
|
|
||||||
$fieldNameParts = explode('.', $fieldName) ;
|
|
||||||
$tmpItem = $this->item;
|
|
||||||
|
|
||||||
for($j=0;$j<sizeof($fieldNameParts);$j++) {
|
|
||||||
$relationMethod = $fieldNameParts[$j];
|
|
||||||
$idField = $relationMethod . 'ID';
|
|
||||||
if($j == sizeof($fieldNameParts)-1) {
|
|
||||||
if($tmpItem) $value = $tmpItem->$relationMethod;
|
|
||||||
} else {
|
|
||||||
if($tmpItem) $tmpItem = $tmpItem->$relationMethod();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// casting
|
|
||||||
if(array_key_exists($fieldName, $this->parent->fieldCasting)) {
|
|
||||||
$value = $this->parent->getCastedValue($value, $this->parent->fieldCasting[$fieldName]);
|
|
||||||
} elseif(is_object($value) && method_exists($value, 'Nice')) {
|
|
||||||
$value = $value->Nice();
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatting
|
|
||||||
$item = $this->item;
|
|
||||||
if(array_key_exists($fieldName, $this->parent->fieldFormatting)) {
|
|
||||||
$format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$fieldName]);
|
|
||||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
|
|
||||||
$format = str_replace('__VAL__', '$value', $format);
|
|
||||||
eval('$value = "' . $format . '";');
|
|
||||||
}
|
|
||||||
|
|
||||||
//escape
|
|
||||||
if($escape = $this->parent->getGridField()->fieldEscape){
|
|
||||||
foreach($escape as $search => $replace){
|
|
||||||
$value = str_replace($search, $replace, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$arrayData = new ArrayData(array(
|
|
||||||
"Name" => $fieldName,
|
|
||||||
"Title" => $fieldTitle,
|
|
||||||
"Value" => $value
|
|
||||||
));
|
|
||||||
$arrayData->iteratorProperties($counter++, count($list));
|
|
||||||
$fields[] = $arrayData;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList($fields);
|
|
||||||
}
|
|
||||||
}
|
|
589
forms/gridfield/GridField.php
Normal file
589
forms/gridfield/GridField.php
Normal file
@ -0,0 +1,589 @@
|
|||||||
|
<?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>
|
||||||
|
*
|
||||||
|
* @see SS_List
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage fields-relational
|
||||||
|
*/
|
||||||
|
class GridField extends FormField {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $allowed_actions = array(
|
||||||
|
'gridFieldAlterAction'
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var SS_List - the datasource */
|
||||||
|
protected $list = null;
|
||||||
|
|
||||||
|
/** @var string - the classname of the DataObject that the GridField will display. Defaults to the value of $this->list->dataClass */
|
||||||
|
protected $modelClassName = '';
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
public $fieldCasting = array();
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
public $fieldFormatting = array();
|
||||||
|
|
||||||
|
/** @var GridState - the current state of the GridField */
|
||||||
|
protected $state = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var GridFieldConfig
|
||||||
|
*/
|
||||||
|
protected $config = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The components list
|
||||||
|
*/
|
||||||
|
protected $components = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the columns that will be visible
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $displayFields = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dispatcher for column handlers.
|
||||||
|
* Keys are column names and values are GridField_ColumnProvider objects
|
||||||
|
*/
|
||||||
|
protected $columnDispatch = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new GridField field
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $title
|
||||||
|
* @param SS_List $dataList
|
||||||
|
* @param GridFieldConfig $config
|
||||||
|
*/
|
||||||
|
public function __construct($name, $title = null, SS_List $dataList = null, GridFieldConfig $config = null) {
|
||||||
|
parent::__construct($name, $title, null);
|
||||||
|
|
||||||
|
FormField::__construct($name);
|
||||||
|
|
||||||
|
if($dataList) {
|
||||||
|
$this->setList($dataList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$config) {
|
||||||
|
$this->config = $this->getDefaultConfig();
|
||||||
|
} else {
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setComponents($this->config);
|
||||||
|
$this->components[] = new GridState_Component();
|
||||||
|
$this->state = new GridState($this);
|
||||||
|
|
||||||
|
|
||||||
|
$this->addExtraClass('ss-gridfield');
|
||||||
|
$this->requireDefaultCSS();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the modelClass that this field will get it column headers from
|
||||||
|
*
|
||||||
|
* @param string $modelClassName
|
||||||
|
*/
|
||||||
|
public function setModelClass($modelClassName) {
|
||||||
|
$this->modelClassName = $modelClassName;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a dataclass that is a DataObject type that this field should look like.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getModelClass() {
|
||||||
|
if ($this->modelClassName) return $this->modelClassName;
|
||||||
|
if ($this->list && $this->list->dataClass) return $this->list->dataClass;
|
||||||
|
|
||||||
|
throw new LogicException('GridField doesn\'t have a modelClassName, so it doesn\'t know the columns of this grid.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set which Components that this GridFields contain by using a GridFieldConfig
|
||||||
|
*
|
||||||
|
* @param GridFieldConfig $config
|
||||||
|
*/
|
||||||
|
protected function setComponents(GridFieldConfig $config) {
|
||||||
|
$this->components = $config->getComponents();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a default configuration for this gridfield
|
||||||
|
*
|
||||||
|
* @return GridFieldConfig
|
||||||
|
*/
|
||||||
|
protected function getDefaultConfig() {
|
||||||
|
$config = GridFieldConfig::create();
|
||||||
|
$config->addComponent(new GridFieldSortableHeader());
|
||||||
|
$config->addComponent(new GridFieldFilter());
|
||||||
|
$config->addComponent(new GridFieldDefaultColumns());
|
||||||
|
$config->addComponent(new GridFieldPaginator());
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require the default css styling
|
||||||
|
*/
|
||||||
|
protected function requireDefaultCSS() {
|
||||||
|
Requirements::css('sapphire/css/GridField.css');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDisplayFields() {
|
||||||
|
if(!$this->displayFields) {
|
||||||
|
return singleton($this->getModelClass())->summaryFields();
|
||||||
|
}
|
||||||
|
return $this->displayFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return GridFieldConfig
|
||||||
|
*/
|
||||||
|
public function getConfig() {
|
||||||
|
return $this->config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param array $fields
|
||||||
|
*/
|
||||||
|
public function setDisplayFields(array $fields) {
|
||||||
|
if(!is_array($fields)) {
|
||||||
|
throw new InvalidArgumentException('Arguments passed to GridField::setDisplayFields() must be an array');
|
||||||
|
}
|
||||||
|
$this->displayFields = $fields;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $casting
|
||||||
|
*/
|
||||||
|
public function setFieldCasting($casting) {
|
||||||
|
$this->fieldCasting = $casting;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $casting
|
||||||
|
*/
|
||||||
|
public function getFieldCasting() {
|
||||||
|
return $this->fieldCasting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $casting
|
||||||
|
*/
|
||||||
|
public function setFieldFormatting($formatting) {
|
||||||
|
$this->fieldFormatting = $formatting;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $casting
|
||||||
|
*/
|
||||||
|
public function getFieldFormatting() {
|
||||||
|
return $this->fieldFormatting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taken from TablelistField
|
||||||
|
*
|
||||||
|
* @param $value
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getCastedValue($value, $castingDefinition) {
|
||||||
|
if(is_array($castingDefinition)) {
|
||||||
|
$castingParams = $castingDefinition;
|
||||||
|
array_shift($castingParams);
|
||||||
|
$castingDefinition = array_shift($castingDefinition);
|
||||||
|
} else {
|
||||||
|
$castingParams = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strpos($castingDefinition,'->') === false) {
|
||||||
|
$castingFieldType = $castingDefinition;
|
||||||
|
$castingField = DBField::create($castingFieldType, $value);
|
||||||
|
$value = call_user_func_array(array($castingField,'XML'),$castingParams);
|
||||||
|
} else {
|
||||||
|
$fieldTypeParts = explode('->', $castingDefinition);
|
||||||
|
$castingFieldType = $fieldTypeParts[0];
|
||||||
|
$castingMethod = $fieldTypeParts[1];
|
||||||
|
$castingField = DBField::create($castingFieldType, $value);
|
||||||
|
$value = call_user_func_array(array($castingField,$castingMethod),$castingParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the datasource
|
||||||
|
*
|
||||||
|
* @param SS_List $list
|
||||||
|
*/
|
||||||
|
public function setList(SS_List $list) {
|
||||||
|
$this->list = $list;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the datasource
|
||||||
|
*
|
||||||
|
* @return SS_List
|
||||||
|
*/
|
||||||
|
public function getList() {
|
||||||
|
return $this->list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current GridState
|
||||||
|
*
|
||||||
|
* @return GridState
|
||||||
|
*/
|
||||||
|
public function getState($getData=true) {
|
||||||
|
if(!$this->state) {
|
||||||
|
throw new LogicException('State has not been defined');
|
||||||
|
}
|
||||||
|
if($getData) {
|
||||||
|
return $this->state->getData();
|
||||||
|
}
|
||||||
|
return $this->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the whole gridfield rendered with all the attached Elements
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function FieldHolder() {
|
||||||
|
// Get columns
|
||||||
|
$columns = $this->getColumns();
|
||||||
|
|
||||||
|
// Get data
|
||||||
|
$list = $this->getList();
|
||||||
|
foreach($this->components as $item) {
|
||||||
|
if($item instanceof GridField_DataManipulator) {
|
||||||
|
$list = $item->getManipulatedData($this, $list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render headers, footers, etc
|
||||||
|
$content = array(
|
||||||
|
'header' => array(),
|
||||||
|
'body' => array(),
|
||||||
|
'footer' => array(),
|
||||||
|
'before' => array(),
|
||||||
|
'after' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach($this->components as $item) {
|
||||||
|
if($item instanceof GridField_HTMLProvider) {
|
||||||
|
$fragments = $item->getHTMLFragments($this);
|
||||||
|
foreach($fragments as $k => $v) {
|
||||||
|
$content[$k][] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($list as $idx => $record) {
|
||||||
|
$record->iteratorProperties($idx, $list->count());
|
||||||
|
$row = "<tr class='".$record->FirstLast()." ".$record->EvenOdd()."'>";
|
||||||
|
foreach($columns as $column) {
|
||||||
|
$colContent = $this->getColumnContent($record, $column);
|
||||||
|
// A return value of null means this columns should be skipped altogether.
|
||||||
|
if($colContent === null) continue;
|
||||||
|
$colAttributes = $this->getColumnAttributes($record, $column);
|
||||||
|
$row .= $this->createTag('td', $colAttributes, $colContent);
|
||||||
|
}
|
||||||
|
$row .= "</tr>";
|
||||||
|
$content['body'][] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn into the relevant parts of a table
|
||||||
|
$head = $content['header'] ? $this->createTag('thead', array(), implode("\n", $content['header'])) : '';
|
||||||
|
$body = $content['body'] ? $this->createTag('tbody', array(), implode("\n", $content['body'])) : '';
|
||||||
|
$foot = $content['footer'] ? $this->createTag('tfoot', array(), implode("\n", $content['footer'])) : '';
|
||||||
|
|
||||||
|
$attrs = array(
|
||||||
|
'id' => isset($this->id) ? $this->id : null,
|
||||||
|
'class' => "field CompositeField {$this->extraClass()}"
|
||||||
|
);
|
||||||
|
return
|
||||||
|
implode("\n", $content['before']) .
|
||||||
|
$this->createTag('table', $attrs, $head."\n".$foot."\n".$body) .
|
||||||
|
implode("\n", $content['after']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumns() {
|
||||||
|
// Get column list
|
||||||
|
$columns = array();
|
||||||
|
foreach($this->components as $item) {
|
||||||
|
if($item instanceof GridField_ColumnProvider) {
|
||||||
|
$item->augmentColumns($this, $columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColumnContent($record, $column) {
|
||||||
|
// Build the column dispatch
|
||||||
|
if(!$this->columnDispatch) $this->buildColumnDispatch();
|
||||||
|
|
||||||
|
$handler = $this->columnDispatch[$column];
|
||||||
|
if($handler) {
|
||||||
|
return $handler->getColumnContent($this, $record, $column);
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentException("Bad column '$column'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColumnAttributes($record, $column) {
|
||||||
|
// Build the column dispatch
|
||||||
|
if(!$this->columnDispatch) $this->buildColumnDispatch();
|
||||||
|
|
||||||
|
$handler = $this->columnDispatch[$column];
|
||||||
|
if($handler) {
|
||||||
|
$attrs = $handler->getColumnAttributes($this, $record, $column);
|
||||||
|
if(is_array($attrs)) return $attrs;
|
||||||
|
else if($attrs) throw new LogicException("Non-array response from " . get_class($handler) . "::getColumnAttributes()");
|
||||||
|
else return array();
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentException("Bad column '$column'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColumnMetadata($column) {
|
||||||
|
// Build the column dispatch
|
||||||
|
if(!$this->columnDispatch) $this->buildColumnDispatch();
|
||||||
|
|
||||||
|
$handler = $this->columnDispatch[$column];
|
||||||
|
if($handler) {
|
||||||
|
$metadata = $handler->getColumnMetadata($this, $column);
|
||||||
|
if(is_array($metadata)) return $metadata;
|
||||||
|
else if($metadata) throw new LogicException("Non-array response from " . get_class($handler) . "::getColumnMetadata()");
|
||||||
|
else return array();
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentException("Bad column '$column'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColumnCount() {
|
||||||
|
// Build the column dispatch
|
||||||
|
if(!$this->columnDispatch) $this->buildColumnDispatch();
|
||||||
|
|
||||||
|
return count($this->columnDispatch);
|
||||||
|
|
||||||
|
}
|
||||||
|
protected function buildColumnDispatch() {
|
||||||
|
$this->columnDispatch = array();
|
||||||
|
foreach($this->components as $item) {
|
||||||
|
if($item instanceof GridField_ColumnProvider) {
|
||||||
|
$columns = $item->getColumnsHandled($this);
|
||||||
|
foreach($columns as $column) {
|
||||||
|
$this->columnDispatch[$column] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the action that gets executed when a GridField_AlterAction gets clicked.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function gridFieldAlterAction($data, $form, $request) {
|
||||||
|
$id = $data['StateID'];
|
||||||
|
$stateChange = Session::get($id);
|
||||||
|
|
||||||
|
$state = $this->getState(false);
|
||||||
|
$state->setValue($data['GridState']);
|
||||||
|
|
||||||
|
$gridName = $stateChange['grid'];
|
||||||
|
$grid = $form->Fields()->fieldByName($gridName);
|
||||||
|
$actionName = $stateChange['actionName'];
|
||||||
|
|
||||||
|
$args = $stateChange['args'];
|
||||||
|
$grid->handleAction($actionName, $args, $data);
|
||||||
|
|
||||||
|
// Make the form re-load it's values from the Session after redirect
|
||||||
|
// so the changes we just made above survive the page reload
|
||||||
|
// @todo Form really needs refactoring so we dont have to do this
|
||||||
|
if (Director::is_ajax()) {
|
||||||
|
return $form->forTemplate();
|
||||||
|
} else {
|
||||||
|
$data = $form->getData();
|
||||||
|
Session::set("FormInfo.{$form->FormName()}.errors", array());
|
||||||
|
Session::set("FormInfo.{$form->FormName()}.data", $data);
|
||||||
|
Controller::curr()->redirectBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleAction($actionName, $args, $data) {
|
||||||
|
$actionName = strtolower($actionName);
|
||||||
|
foreach($this->components as $item) {
|
||||||
|
if(!($item instanceof GridField_ActionProvider)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(in_array($actionName, array_map('strtolower', $item->getActions($this)))) {
|
||||||
|
return $item->handleAction($this, $actionName, $args, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new InvalidArgumentException("Can't handle action '$actionName'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the base class when you want to have an action that alters the state of the gridfield
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage forms
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class GridField_Action extends FormAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var GridField
|
||||||
|
*/
|
||||||
|
protected $gridField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $buttonLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $stateValues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
//protected $stateFields = array();
|
||||||
|
|
||||||
|
protected $actionName;
|
||||||
|
protected $args = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @param type $name
|
||||||
|
* @param type $label
|
||||||
|
* @param type $actionName
|
||||||
|
* @param type $args
|
||||||
|
*/
|
||||||
|
public function __construct(GridField $gridField, $name, $label, $actionName, $args) {
|
||||||
|
$this->gridField = $gridField;
|
||||||
|
$this->buttonLabel = $label;
|
||||||
|
$this->actionName = $actionName;
|
||||||
|
$this->args = $args;
|
||||||
|
parent::__construct($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* urlencode encodes less characters in percent form than we need - we need everything that isn't a \w
|
||||||
|
*
|
||||||
|
* @param string $val
|
||||||
|
*/
|
||||||
|
public function nameEncode($val) {
|
||||||
|
return preg_replace_callback('/[^\w]/', array($this, '_nameEncode'), $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The callback for nameEncode
|
||||||
|
*
|
||||||
|
* @param string $val
|
||||||
|
*/
|
||||||
|
public function _nameEncode($match) {
|
||||||
|
return '%'.dechex(ord($match[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default method used by Templates to render the form
|
||||||
|
*
|
||||||
|
* @return string HTML tag
|
||||||
|
*/
|
||||||
|
public function Field() {
|
||||||
|
// Store state in session, and pass ID to client side
|
||||||
|
$state = array(
|
||||||
|
'grid' => $this->getNameFromParent(),
|
||||||
|
'actionName' => $this->actionName,
|
||||||
|
'args' => $this->args,
|
||||||
|
);
|
||||||
|
|
||||||
|
$id = preg_replace('/[^\w]+/', '_', uniqid('', true));
|
||||||
|
Session::set($id, $state);
|
||||||
|
|
||||||
|
$actionData['StateID'] = $id;
|
||||||
|
|
||||||
|
// And generate field
|
||||||
|
$attributes = array(
|
||||||
|
'class' => 'action' . ($this->extraClass() ? $this->extraClass() : ''),
|
||||||
|
'id' => $this->id(),
|
||||||
|
'type' => 'submit',
|
||||||
|
// Note: This field needs to be less than 65 chars, otherwise Suhosin security patch
|
||||||
|
// will strip it from the requests
|
||||||
|
'name' => 'action_gridFieldAlterAction'. '?' . http_build_query($actionData),
|
||||||
|
'tabindex' => $this->getTabIndex(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if($this->isReadonly()) {
|
||||||
|
$attributes['disabled'] = 'disabled';
|
||||||
|
$attributes['class'] = $attributes['class'] . ' disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->createTag('button', $attributes, $this->buttonLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the name of the gridfield relative to the Form
|
||||||
|
*
|
||||||
|
* @param GridField $base
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getNameFromParent() {
|
||||||
|
$base = $this->gridField;
|
||||||
|
$name = array();
|
||||||
|
do {
|
||||||
|
array_unshift($name, $base->getName());
|
||||||
|
$base = $base->getForm();
|
||||||
|
} while ($base && !($base instanceof Form));
|
||||||
|
return implode('.', $name);
|
||||||
|
}
|
||||||
|
}
|
44
forms/gridfield/GridFieldComponent.php
Normal file
44
forms/gridfield/GridFieldComponent.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interface for all components that can be added to GridField.
|
||||||
|
*/
|
||||||
|
interface GridFieldComponent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A GridField manipulator that provides HTML for the header/footer rows, or for before/after the template
|
||||||
|
*/
|
||||||
|
interface GridField_HTMLProvider extends GridFieldComponent {
|
||||||
|
/**
|
||||||
|
* Returns a map with 4 keys 'header', 'fooer', 'before', 'after'. Each of these can contain an
|
||||||
|
* HTML fragment and each of these are optional.
|
||||||
|
*/
|
||||||
|
function getHTMLFragments($gridField);
|
||||||
|
}
|
||||||
|
interface GridField_ColumnProvider extends GridFieldComponent {
|
||||||
|
function augmentColumns($gridField, &$columns);
|
||||||
|
function getColumnsHandled($gridField);
|
||||||
|
function getColumnContent($gridField, $record, $columnName);
|
||||||
|
function getColumnAttributes($gridField, $record, $columnName);
|
||||||
|
function getColumnMetadata($gridField, $columnName);
|
||||||
|
}
|
||||||
|
interface GridField_ActionProvider extends GridFieldComponent {
|
||||||
|
/**
|
||||||
|
* Return a list of the actions handled by this action provider
|
||||||
|
*/
|
||||||
|
function getActions($gridField);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an action on the given gridField.
|
||||||
|
*/
|
||||||
|
function handleAction(GridField $gridField, $actionName, $arguments, $data);
|
||||||
|
}
|
||||||
|
interface GridField_DataManipulator extends GridFieldComponent {
|
||||||
|
/**
|
||||||
|
* Manipulate the datalist as needed by this grid modifier.
|
||||||
|
* Return the new DataList.
|
||||||
|
*/
|
||||||
|
function getManipulatedData(GridField $gridField, SS_List $dataList);
|
||||||
|
}
|
88
forms/gridfield/GridFieldConfig.php
Normal file
88
forms/gridfield/GridFieldConfig.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Description of GridFieldConfig
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class GridFieldConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return GridFieldConfig
|
||||||
|
*/
|
||||||
|
public static function create(){
|
||||||
|
return new GridFieldConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var ArrayList
|
||||||
|
*/
|
||||||
|
protected $components = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $checkboxes = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $affectors = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $decorators = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addComponent(GridFieldComponent $component) {
|
||||||
|
$this->getComponents()->push($component);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return ArrayList
|
||||||
|
*/
|
||||||
|
public function getComponents() {
|
||||||
|
if(!$this->components) {
|
||||||
|
$this->components = new ArrayList();
|
||||||
|
}
|
||||||
|
return $this->components;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCheckboxes($row=0){
|
||||||
|
$this->checkboxes = $row;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCheckboxes() {
|
||||||
|
return $this->checkboxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAffector(GridState_Affector $affector) {
|
||||||
|
$this->affectors[] = $affector;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAffectors() {
|
||||||
|
return $this->affectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addDecorator($decorator) {
|
||||||
|
$this->decorators[] = $decorator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDecorators() {
|
||||||
|
return $this->decorators;
|
||||||
|
}
|
||||||
|
}
|
120
forms/gridfield/GridFieldDefaultColumns.php
Normal file
120
forms/gridfield/GridFieldDefaultColumns.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @see GridField
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage fields-relational
|
||||||
|
*/
|
||||||
|
class GridFieldDefaultColumns implements GridField_ColumnProvider {
|
||||||
|
|
||||||
|
public function augmentColumns($gridField, &$columns) {
|
||||||
|
$baseColumns = array_keys($gridField->getDisplayFields());
|
||||||
|
foreach($baseColumns as $col) $columns[] = $col;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColumnsHandled($gridField) {
|
||||||
|
return array_keys($gridField->getDisplayFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param string $fieldName
|
||||||
|
* @param string $value
|
||||||
|
* @param boolean $xmlSafe
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
public function getColumnContent($gridField, $item, $column) {
|
||||||
|
// Find the data column for the given named column
|
||||||
|
$fieldName = $column;
|
||||||
|
$xmlSafe = true;
|
||||||
|
|
||||||
|
// This supports simple FieldName syntax
|
||||||
|
if(strpos($fieldName, '.') === false) {
|
||||||
|
return ($item->XML_val($fieldName) && $xmlSafe) ? $item->XML_val($fieldName) : $item->RAW_val($fieldName);
|
||||||
|
}
|
||||||
|
$fieldNameParts = explode('.', $fieldName);
|
||||||
|
$tmpItem = $item;
|
||||||
|
for($idx = 0; $idx < sizeof($fieldNameParts); $idx++) {
|
||||||
|
$relationMethod = $fieldNameParts[$idx];
|
||||||
|
// Last value for value
|
||||||
|
if($idx == sizeof($fieldNameParts) - 1) {
|
||||||
|
if($tmpItem) {
|
||||||
|
return ($tmpItem->XML_val($relationMethod) && $xmlSafe) ? $tmpItem->XML_val($relationMethod) : $tmpItem->RAW_val($relationMethod);
|
||||||
|
}
|
||||||
|
// else get the object for the next iteration
|
||||||
|
} else {
|
||||||
|
if($tmpItem) {
|
||||||
|
$tmpItem = $tmpItem->$relationMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $this->castValue($gridField, $column, $value);
|
||||||
|
$value = $this->formatValue($gridField, $item, $column, $value);
|
||||||
|
$value = $this->escapeValue($gridField, $value);
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColumnAttributes($gridField, $item, $column) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColumnMetadata($gridField, $column) {
|
||||||
|
$columns = $gridField->getDisplayFields();
|
||||||
|
return array(
|
||||||
|
'title' => $columns[$column],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param type $fieldName
|
||||||
|
* @param type $value
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
protected function castValue($gridField, $fieldName, $value) {
|
||||||
|
if(array_key_exists($fieldName, $gridField->FieldCasting)) {
|
||||||
|
return $gridField->getCastedValue($value, $gridField->FieldCasting[$fieldName]);
|
||||||
|
} elseif(is_object($value) && method_exists($value, 'Nice')) {
|
||||||
|
return $value->Nice();
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param type $fieldName
|
||||||
|
* @param type $value
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
protected function formatValue($gridField, $item, $fieldName, $value) {
|
||||||
|
if(!array_key_exists($fieldName, $gridField->FieldFormatting)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$format = str_replace('$value', "__VAL__", $gridField->FieldFormatting[$fieldName]);
|
||||||
|
$format = preg_replace('/\$([A-Za-z0-9-_]+)/', '$item->$1', $format);
|
||||||
|
$format = str_replace('__VAL__', '$value', $format);
|
||||||
|
eval('$value = "' . $format . '";');
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove values from a value using FieldEscape setter
|
||||||
|
*
|
||||||
|
* @param type $value
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
protected function escapeValue($gridField, $value) {
|
||||||
|
if(!$escape = $gridField->FieldEscape) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($escape as $search => $replace) {
|
||||||
|
$value = str_replace($search, $replace, $value);
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
105
forms/gridfield/GridFieldFilter.php
Normal file
105
forms/gridfield/GridFieldFilter.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* GridFieldFilter alters the gridfield with some filtering fields in the header of each column
|
||||||
|
*
|
||||||
|
* @see GridField
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage fields-relational
|
||||||
|
*/
|
||||||
|
class GridFieldFilter implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $location = 'head';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getActions($gridField) {
|
||||||
|
return array('filter', 'reset');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAction(GridField $gridField, $actionName, $arguments, $data) {
|
||||||
|
$state = $gridField->State->GridFieldFilter;
|
||||||
|
if($actionName === 'filter') {
|
||||||
|
if(isset($data['filter'])){
|
||||||
|
foreach($data['filter'] as $key => $filter ){
|
||||||
|
$state->Columns->$key = $filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif($actionName === 'reset') {
|
||||||
|
$state->Columns = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @param SS_List $dataList
|
||||||
|
* @return SS_List
|
||||||
|
*/
|
||||||
|
public function getManipulatedData(GridField $gridField, SS_List $dataList) {
|
||||||
|
$state = $gridField->State->GridFieldFilter;
|
||||||
|
if(!isset($state->Columns)) {
|
||||||
|
return $dataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filterArguments = $state->Columns->toArray();
|
||||||
|
foreach($filterArguments as $columnName => $value ) {
|
||||||
|
if($dataList->canFilterBy($columnName) && $value) {
|
||||||
|
$dataList->filter($columnName.':PartialMatch', $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $dataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHTMLFragments($gridField) {
|
||||||
|
Requirements::javascript(SAPPHIRE_DIR.'/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
|
||||||
|
Requirements::javascript('sapphire/javascript/GridField.js');
|
||||||
|
|
||||||
|
$forTemplate = new ArrayData(array());
|
||||||
|
$forTemplate->Fields = new ArrayList;
|
||||||
|
|
||||||
|
$columns = $gridField->getColumns();
|
||||||
|
$filterArguments = $gridField->State->GridFieldFilter->Columns->toArray();
|
||||||
|
|
||||||
|
$currentColumn = 0;
|
||||||
|
foreach($columns as $columnField) {
|
||||||
|
$currentColumn++;
|
||||||
|
$metadata = $gridField->getColumnMetadata($columnField);
|
||||||
|
$title = $metadata['title'];
|
||||||
|
if($title && $gridField->getList()->canFilterBy($columnField)) {
|
||||||
|
$value = '';
|
||||||
|
if(isset($filterArguments[$columnField])) {
|
||||||
|
$value = $filterArguments[$columnField];
|
||||||
|
}
|
||||||
|
$field = new TextField('filter['.$columnField.']', 'filter['.$columnField.']', $value);
|
||||||
|
$field->addExtraClass('ss-gridfield-sort');
|
||||||
|
} else {
|
||||||
|
$field = new LiteralField('', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last column, inject action buttons
|
||||||
|
if($currentColumn == count($columns)) {
|
||||||
|
$field = new FieldGroup(
|
||||||
|
$field,
|
||||||
|
new GridField_Action($gridField, 'filter', 'filter', 'filter', null),
|
||||||
|
new GridField_Action($gridField, 'reset', 'reset', 'reset', null)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
$field->iteratorProperties($currentColumn-1, count($columns));
|
||||||
|
$forTemplate->Fields->push($field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'header' => $forTemplate->renderWith('GridFieldFilter_Row'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
90
forms/gridfield/GridFieldPaginator.php
Normal file
90
forms/gridfield/GridFieldPaginator.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GridFieldPaginator decorates the GridFieldPresenter with the possibility to
|
||||||
|
* paginate the GridField.
|
||||||
|
*
|
||||||
|
* @see GridField
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage fields-relational
|
||||||
|
*/
|
||||||
|
class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {
|
||||||
|
|
||||||
|
protected $currentPage = 1;
|
||||||
|
|
||||||
|
protected $itemsPerPage = 25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which template to use for rendering
|
||||||
|
*
|
||||||
|
* @var string $itemClass
|
||||||
|
*/
|
||||||
|
protected $itemClass = 'GridFieldPaginator_Row';
|
||||||
|
|
||||||
|
public function __construct($itemsPerPage=25) {
|
||||||
|
$this->itemsPerPage = $itemsPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActions($gridField) {
|
||||||
|
return array('paginate');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
|
||||||
|
if($actionName !== 'paginate') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$state = $gridField->State->GridFieldPaginator;
|
||||||
|
$this->currentPage = $state->currentPage = (int)$arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Duck check to see if list support methods we need to paginate */
|
||||||
|
protected function getListPaginatable(SS_List $list) {
|
||||||
|
// If no list yet, not paginatable
|
||||||
|
if (!$list) return false;
|
||||||
|
// Check for methods we use
|
||||||
|
if(!method_exists($list, 'getRange')) return false;
|
||||||
|
if(!method_exists($list, 'limit')) return false;
|
||||||
|
// Default it true
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getManipulatedData(GridField $gridField, SS_List $dataList) {
|
||||||
|
if(!$this->getListPaginatable($dataList)) {
|
||||||
|
return $dataList;
|
||||||
|
}
|
||||||
|
if(!$this->currentPage) {
|
||||||
|
return $dataList->getRange(0, (int)$this->itemsPerPage);
|
||||||
|
}
|
||||||
|
$startRow = $this->itemsPerPage*($this->currentPage-1);
|
||||||
|
return $dataList->getRange((int)$startRow, (int)$this->itemsPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHTMLFragments($gridField) {
|
||||||
|
Requirements::javascript(SAPPHIRE_DIR.'/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
|
||||||
|
Requirements::javascript(SAPPHIRE_DIR.'/javascript/GridField.js');
|
||||||
|
|
||||||
|
$forTemplate = new ArrayData(array());
|
||||||
|
$forTemplate->Fields = new ArrayList;
|
||||||
|
|
||||||
|
$countList = clone $gridField->List;
|
||||||
|
$totalRows = $countList->limit(null)->count();
|
||||||
|
$totalPages = ceil($totalRows/$this->itemsPerPage);
|
||||||
|
for($idx=1; $idx<=$totalPages; $idx++) {
|
||||||
|
if($idx == $this->currentPage) {
|
||||||
|
$field = new LiteralField('pagination_'.$idx, $idx);
|
||||||
|
} else {
|
||||||
|
$field = new GridField_Action($gridField, 'pagination_'.$idx, $idx, 'paginate', $idx);
|
||||||
|
$field->addExtraClass('ss-gridfield-button');
|
||||||
|
}
|
||||||
|
|
||||||
|
$forTemplate->Fields->push($field);
|
||||||
|
}
|
||||||
|
if(!$forTemplate->Fields->Count()) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
return array(
|
||||||
|
'footer' => $forTemplate->renderWith('GridFieldPaginator_Row', array('Colspan'=>count($gridField->getColumns()))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
82
forms/gridfield/GridFieldSortableHeader.php
Normal file
82
forms/gridfield/GridFieldSortableHeader.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* GridFieldSortableHeader adds column headers to a gridfield that can also sort the columns
|
||||||
|
*
|
||||||
|
* @see GridField
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage fields-relational
|
||||||
|
*/
|
||||||
|
class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the header row providing titles with sort buttons
|
||||||
|
*/
|
||||||
|
public function getHTMLFragments($gridField) {
|
||||||
|
Requirements::javascript(SAPPHIRE_DIR.'/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
|
||||||
|
Requirements::javascript(SAPPHIRE_DIR.'/javascript/GridField.js');
|
||||||
|
|
||||||
|
$forTemplate = new ArrayData(array());
|
||||||
|
$forTemplate->Fields = new ArrayList;
|
||||||
|
|
||||||
|
$state = $gridField->State->GridFieldSortableHeader;
|
||||||
|
$columns = $gridField->getColumns();
|
||||||
|
|
||||||
|
foreach($columns as $columnField) {
|
||||||
|
$metadata = $gridField->getColumnMetadata($columnField);
|
||||||
|
$title = $metadata['title'];
|
||||||
|
if($title && $gridField->getList()->canSortBy($columnField)) {
|
||||||
|
$dir = 'asc';
|
||||||
|
if($state->SortColumn == $columnField && $state->SortDirection == 'asc') {
|
||||||
|
$dir = 'desc';
|
||||||
|
}
|
||||||
|
|
||||||
|
$field = new GridField_Action($gridField, 'SetOrder'.$columnField, $title, "sort$dir", array('SortColumn' => $columnField));
|
||||||
|
|
||||||
|
$field->addExtraClass('ss-gridfield-sort');
|
||||||
|
if($state->SortColumn == $columnField){
|
||||||
|
$field->addExtraClass('ss-gridfield-sorted');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$field = new LiteralField($columnField, $title);
|
||||||
|
}
|
||||||
|
$forTemplate->Fields->push($field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'header' => $forTemplate->renderWith('GridFieldSortableHeader_Row'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param GridField $gridField
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getActions($gridField) {
|
||||||
|
return array('sortasc', 'sortdesc');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAction(GridField $gridField, $actionName, $arguments, $data) {
|
||||||
|
$state = $gridField->State->GridFieldSortableHeader;
|
||||||
|
switch($actionName) {
|
||||||
|
case 'sortasc':
|
||||||
|
$state->SortColumn = $arguments['SortColumn'];
|
||||||
|
$state->SortDirection = 'asc';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'sortdesc':
|
||||||
|
$state->SortColumn = $arguments['SortColumn'];
|
||||||
|
$state->SortDirection = 'desc';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getManipulatedData(GridField $gridField, SS_List $dataList) {
|
||||||
|
$state = $gridField->State->GridFieldSortableHeader;
|
||||||
|
if ($state->SortColumn == "") {
|
||||||
|
return $dataList;
|
||||||
|
}
|
||||||
|
return $dataList->sort($state->SortColumn, $state->SortDirection);
|
||||||
|
}
|
||||||
|
}
|
148
forms/gridfield/GridState.php
Normal file
148
forms/gridfield/GridState.php
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This class is a snapshot of the current status of a gridfield.
|
||||||
|
*
|
||||||
|
* It's main use is to be inserted into a Form as a HiddenField
|
||||||
|
*
|
||||||
|
* @see GridField
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage fields-relational
|
||||||
|
*/
|
||||||
|
class GridState extends HiddenField {
|
||||||
|
|
||||||
|
/** @var GridField */
|
||||||
|
protected $grid;
|
||||||
|
|
||||||
|
protected $gridStateData = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param type $d
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
public static function array_to_object($d) {
|
||||||
|
if(is_array($d)) {
|
||||||
|
return (object) array_map(array('GridState', 'array_to_object'), $d);
|
||||||
|
} else {
|
||||||
|
return $d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param GridField $name
|
||||||
|
* @param string $data - json encoded string
|
||||||
|
*/
|
||||||
|
public function __construct($grid, $value = null) {
|
||||||
|
$this->grid = $grid;
|
||||||
|
|
||||||
|
if ($value) $this->setValue($value);
|
||||||
|
|
||||||
|
parent::__construct('GridState');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param type $value
|
||||||
|
*/
|
||||||
|
public function setValue($value) {
|
||||||
|
if (is_string($value)) {
|
||||||
|
$this->gridStateData = new GridState_Data(json_decode($value, true));
|
||||||
|
}
|
||||||
|
parent::setValue($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData() {
|
||||||
|
if(!$this->gridStateData) $this->gridStateData = new GridState_Data;
|
||||||
|
return $this->gridStateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
public function getList() {
|
||||||
|
return $this->grid->getList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return string */
|
||||||
|
public function Value() {
|
||||||
|
return json_encode($this->gridStateData->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
public function dataValue() {
|
||||||
|
return $this->Value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
public function attrValue() {
|
||||||
|
return Convert::raw2att($this->Value());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
return $this->Value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple set of data, similar to stdClass, but without the notice-level errors
|
||||||
|
*/
|
||||||
|
class GridState_Data {
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
function __construct($data = array()) {
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __get($name) {
|
||||||
|
if(!isset($this->data[$name])) $this->data[$name] = new GridState_Data;
|
||||||
|
if(is_array($this->data[$name])) $this->data[$name] = new GridState_Data($this->data[$name]);
|
||||||
|
return $this->data[$name];
|
||||||
|
}
|
||||||
|
function __set($name, $value) {
|
||||||
|
$this->data[$name] = $value;
|
||||||
|
}
|
||||||
|
function __isset($name) {
|
||||||
|
return isset($this->data[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __toString() {
|
||||||
|
if(!$this->data) return "";
|
||||||
|
else return json_encode($this->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArray() {
|
||||||
|
$output = array();
|
||||||
|
foreach($this->data as $k => $v) {
|
||||||
|
$output[$k] = (is_object($v) && method_exists($v, 'toArray')) ? $v->toArray() : $v;
|
||||||
|
}
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GridState_Component implements GridField_HTMLProvider {
|
||||||
|
|
||||||
|
public function getHTMLFragments($gridField) {
|
||||||
|
|
||||||
|
$forTemplate = new ArrayData(array());
|
||||||
|
$forTemplate->Fields = new ArrayList;
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'before' => $gridField->getState(false)->Field()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
70
javascript/GridField.js
Normal file
70
javascript/GridField.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
jQuery(function($){
|
||||||
|
|
||||||
|
$('.ss-gridfield .action').entwine({
|
||||||
|
onclick: function(e){
|
||||||
|
button = this;
|
||||||
|
e.preventDefault();
|
||||||
|
var form = $(this).closest("form");
|
||||||
|
form.addClass('loading');
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: form.attr('action'),
|
||||||
|
data: form.serialize()+'&'+escape(button.attr('name'))+'='+escape(button.val()),
|
||||||
|
dataType: 'html',
|
||||||
|
success: function(data) {
|
||||||
|
form.replaceWith(data);
|
||||||
|
form.removeClass('loading');
|
||||||
|
},
|
||||||
|
error: function(e) {
|
||||||
|
alert(ss.i18n._t('GRIDFIELD.ERRORINTRANSACTION', 'An error occured while fetching data from the server\n Please try again later.'));
|
||||||
|
form.removeClass('loading');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var removeFilterButtons = function() {
|
||||||
|
// Remove stuff
|
||||||
|
$('th').children('div').each(function(i,v) {
|
||||||
|
$(v).remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Upon focusing on a filter <input> element, move "filter" and "reset" buttons and display next to the current <input> element
|
||||||
|
* ToDo ensure filter-button state is maintained after filtering (see resetState param)
|
||||||
|
* ToDo get working in IE 6-7
|
||||||
|
*/
|
||||||
|
$('.ss-gridfield input.ss-gridfield-sort').entwine({
|
||||||
|
onfocusin: function(e) {
|
||||||
|
// Dodgy results in IE <=7
|
||||||
|
if($.browser.msie && $.browser.version <= 7) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var eleInput = $(this);
|
||||||
|
// Remove existing <div> and <button> elements in-lieu of cloning
|
||||||
|
removeFilterButtons();
|
||||||
|
var eleButtonSetFilter = $('#action_filter');
|
||||||
|
var eleButtonResetFilter = $('#action_reset');
|
||||||
|
// Retain current widths to ensure <th>'s don't shift widths
|
||||||
|
var eleButtonWidth = eleButtonSetFilter.width();
|
||||||
|
// Check <th> doesn't already have an (extra) cloned <button> appended, otherwise clone
|
||||||
|
if(eleInput.closest('th').children().length == 1) {
|
||||||
|
var newButtonCss = {
|
||||||
|
'position':'absolute',
|
||||||
|
'top':'-23px',
|
||||||
|
'left':'0',
|
||||||
|
'border':'#EEE solid 1px',
|
||||||
|
'padding':'0',
|
||||||
|
'margin-left':'0'
|
||||||
|
};
|
||||||
|
// Append a <div> element used purely for CSS positioning - table elements on their own are untrustworthy to style in this manner
|
||||||
|
$('<div/>').append(
|
||||||
|
eleButtonSetFilter.clone().css(newButtonCss),
|
||||||
|
eleButtonResetFilter.clone().css(newButtonCss).css('left',(eleButtonWidth+4)+'px')
|
||||||
|
).css({'position':'relative','margin':'0 auto','width':'65%'}).appendTo(eleInput.closest('th'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -1,30 +0,0 @@
|
|||||||
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();
|
|
||||||
|
|
||||||
});
|
|
@ -14,6 +14,8 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
|
|||||||
'FILEIFRAMEFIELD.DELETEFILE': 'Delete File',
|
'FILEIFRAMEFIELD.DELETEFILE': 'Delete File',
|
||||||
'FILEIFRAMEFIELD.UNATTACHFILE': 'Un-Attach File',
|
'FILEIFRAMEFIELD.UNATTACHFILE': 'Un-Attach File',
|
||||||
'FILEIFRAMEFIELD.DELETEIMAGE': 'Delete Image',
|
'FILEIFRAMEFIELD.DELETEIMAGE': 'Delete Image',
|
||||||
'FILEIFRAMEFIELD.CONFIRMDELETE': 'Are you sure you want to delete this file?'
|
'FILEIFRAMEFIELD.CONFIRMDELETE': 'Are you sure you want to delete this file?',
|
||||||
|
'LeftAndMain.IncompatBrowserWarning': 'Your browser is not compatible with the CMS interface. Please use Internet Explorer 7+, Google Chrome 10+ or Mozilla Firefox 3.5+.',
|
||||||
|
'GRIDFIELD.ERRORINTRANSACTION': 'An error occured while fecthing data from the server\n Please try again later.'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
|
|||||||
'UNIQUEFIELD.ENTERNEWVALUE': 'You devez saisir une nouvelle valeur pou ce champ',
|
'UNIQUEFIELD.ENTERNEWVALUE': 'You devez saisir une nouvelle valeur pou ce champ',
|
||||||
'UNIQUEFIELD.CANNOTLEAVEEMPTY': 'Ce champ ne peut être laissé vide',
|
'UNIQUEFIELD.CANNOTLEAVEEMPTY': 'Ce champ ne peut être laissé vide',
|
||||||
'RESTRICTEDTEXTFIELD.CHARCANTBEUSED': "Le character '%s' ne peut être utilisé dans ce champ",
|
'RESTRICTEDTEXTFIELD.CHARCANTBEUSED': "Le character '%s' ne peut être utilisé dans ce champ",
|
||||||
'UPDATEURL.CONFIRM': 'Voulez-vous que je change l\'URL en:\n\n%s/\n\nCliquez Ok pour changer l\'URL, cliquez Annuler pour la laisser à:\n\n%s'
|
'UPDATEURL.CONFIRM': 'Voulez-vous que je change l\'URL en:\n\n%s/\n\nCliquez Ok pour changer l\'URL, cliquez Annuler pour la laisser à:\n\n%s',
|
||||||
|
'GRIDFIELD.ERRORINTRANSACTION': 'Une erreur est survenue durant la transaction avec le serveur\n Merci de reesayer plus tard.'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,18 @@ class DataList extends ViewableData implements SS_List {
|
|||||||
public function canSortBy($fieldName) {
|
public function canSortBy($fieldName) {
|
||||||
return $this->dataQuery()->query()->canSortBy($fieldName);
|
return $this->dataQuery()->query()->canSortBy($fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param string $fieldName
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function canFilterBy($fieldName) {
|
||||||
|
if($t = singleton($this->dataClass)->hasDatabaseField($fieldName)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an join clause to this data list's query.
|
* Add an join clause to this data list's query.
|
||||||
@ -539,7 +551,8 @@ class DataList extends ViewableData implements SS_List {
|
|||||||
*/
|
*/
|
||||||
public function byID($id) {
|
public function byID($id) {
|
||||||
$baseClass = ClassInfo::baseDataClass($this->dataClass);
|
$baseClass = ClassInfo::baseDataClass($this->dataClass);
|
||||||
return $this->where("\"$baseClass\".\"ID\" = " . (int)$id)->First();
|
$clone = clone $this;
|
||||||
|
return $clone->where("\"$baseClass\".\"ID\" = " . (int)$id)->First();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,116 +3,216 @@
|
|||||||
*
|
*
|
||||||
* @package sapphire
|
* @package sapphire
|
||||||
* @subpackage scss
|
* @subpackage scss
|
||||||
|
* @todo Add radial gradient to default delete button state
|
||||||
|
* @todo Create SASS mixin-function to simply swap the from/to, to to/from colours in grsdient mixins?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@import "compass/css3";
|
@import "compass/css3";
|
||||||
|
|
||||||
$base: #dadada;
|
$gf_colour_gradient_light: #B1C0C5;
|
||||||
$zebra: #f2f9fd;
|
$gf_colour_gradient_dark: #7F9198;
|
||||||
|
$gf_colour_base: #95a5ab;
|
||||||
|
$gf_colour_header_border: #819198;
|
||||||
|
$gf_colour_border: #DBDDDD;
|
||||||
|
$gf_colour_zebra: #f2f9fd;
|
||||||
|
$gf_border_radius: 7px;
|
||||||
|
|
||||||
.ss-gridfield {
|
@mixin box-shadow-none {
|
||||||
border: none;
|
-moz-box-shadow: none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
table {
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin gridfield-gradient-top {
|
||||||
|
background: $gf_colour_gradient_light;
|
||||||
|
background: -moz-linear-gradient($gf_colour_gradient_light 20%, $gf_colour_gradient_dark); /* FF3.6+ */
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(20%,$gf_colour_gradient_light), color-stop(80%,$gf_colour_gradient_dark)); /* Chrome,Safari4+ */
|
||||||
|
background: -webkit-linear-gradient(top, $gf_colour_gradient_light 20%,$gf_colour_gradient_dark 80%); /* Chrome10+,Safari5.1+ */
|
||||||
|
background: -o-linear-gradient(top, $gf_colour_gradient_light 20%,$gf_colour_gradient_dark 80%); /* Opera 11.10+ */
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#{$gf_colour_gradient_light}', endColorstr='#{$gf_colour_gradient_dark}'); /* IE5.5+ */
|
||||||
|
background: -ms-linear-gradient(top, $gf_colour_gradient_light 20%,$gf_colour_gradient_dark 80%); /* IE10+ */
|
||||||
|
background: linear-gradient(top, $gf_colour_gradient_light 20%,$gf_colour_gradient_dark 80%); /* W3C */
|
||||||
|
}
|
||||||
|
@mixin gridfield-gradient-bottom {
|
||||||
|
background: $gf_colour_gradient_dark;
|
||||||
|
background: -moz-linear-gradient($gf_colour_gradient_dark 20%, $gf_colour_gradient_light); /* FF3.6+ */
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(20%,$gf_colour_gradient_dark), color-stop(80%,$gf_colour_gradient_light)); /* Chrome,Safari4+ */
|
||||||
|
background: -webkit-linear-gradient(top, $gf_colour_gradient_dark 20%,$gf_colour_gradient_light 80%); /* Chrome10+,Safari5.1+ */
|
||||||
|
background: -o-linear-gradient(top, $gf_colour_gradient_dark 20%,$gf_colour_gradient_light 80%); /* Opera 11.10+ */
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#{$gf_colour_gradient_dark}', endColorstr='#{$gf_colour_gradient_light}'); /* IE5.5+ */
|
||||||
|
background: -ms-linear-gradient(top, $gf_colour_gradient_dark 20%,$gf_colour_gradient_dark 80%); /* IE10+ */
|
||||||
|
background: linear-gradient(top, $gf_colour_gradient_dark 20%,$gf_colour_gradient_light 80%); /* W3C */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms {
|
||||||
|
table.ss-gridfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
padding: 0;
|
||||||
border-spacing: 0;
|
margin: 20px 0 0 0;
|
||||||
background: #fff;
|
border-collapse: separate;
|
||||||
border: 1px solid darken($base, 10%);
|
display: table;
|
||||||
}
|
border-bottom: 0 none;
|
||||||
|
|
||||||
thead {
|
|
||||||
color: darken($base, 50%);
|
|
||||||
|
|
||||||
background: $base;
|
thead {
|
||||||
|
color: darken($gf_colour_base, 50%);
|
||||||
@include background-image(linear-gradient(
|
background: transparent;
|
||||||
lighten($base, 10%), $base
|
}
|
||||||
));
|
|
||||||
|
tbody {
|
||||||
|
background: #FFF;
|
||||||
|
td {
|
||||||
|
/* Rounded buttons */
|
||||||
|
button {
|
||||||
|
border: #CC0033 solid 1px;
|
||||||
|
background: #CC0033;
|
||||||
|
color: #FFF;
|
||||||
|
@include border-radius(15px);
|
||||||
|
margin: 0 0 0 2px;
|
||||||
|
padding: 0;
|
||||||
|
width: auto;
|
||||||
|
min-width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
text-shadow: none;
|
||||||
|
&:hover {
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tfoot {
|
||||||
th {
|
color: darken($gf_colour_base, 50%);
|
||||||
font-weight: bold;
|
tr {
|
||||||
padding: 8px 24px 8px 8px;
|
td {
|
||||||
position: relative;
|
background: $gf_colour_base;
|
||||||
border: 1px solid darken($base, 10%);
|
padding: .7em;
|
||||||
border-width: 0 1px 1px 0;
|
|
||||||
|
|
||||||
&.ss-gridfield-sortable {
|
|
||||||
|
|
||||||
&.hover {
|
|
||||||
color: darken($base, 40%);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
background: lighten($base, 10%);
|
|
||||||
|
|
||||||
@include background-image(linear-gradient(
|
|
||||||
lighten($base, 15%), lighten($base, 5%)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ss-gridfield-sorted {
|
|
||||||
background: lighten($base, 5%);
|
|
||||||
|
|
||||||
@include background-image(linear-gradient(
|
|
||||||
$base, lighten($base, 10%)
|
|
||||||
));
|
|
||||||
|
|
||||||
&.hover {
|
|
||||||
background: lighten($base, 10%);
|
|
||||||
|
|
||||||
@include background-image(linear-gradient(
|
|
||||||
lighten($base, 5%), lighten($base, 12%)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ss-gridfield-desc .ui-icon {
|
|
||||||
background-position: 0 -48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ss-gridfield-asc .ui-icon {
|
|
||||||
background-position: -64px -48px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
tr {
|
||||||
tbody {
|
&.sortable-header {
|
||||||
|
th {
|
||||||
|
background: $gf_colour_gradient_dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: #FFFAD6 !important;
|
||||||
|
}
|
||||||
|
&:first-child {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
&.ss-gridfield-even {
|
||||||
|
background: $gf_colour_zebra;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 8px;
|
|
||||||
border-right: 1px solid lighten($base, 10%);
|
|
||||||
|
|
||||||
&.ss-gridfield-last {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
|
|
||||||
&.ss-gridfield-even {
|
|
||||||
border: 1px solid darken($zebra, 10%);
|
|
||||||
border-width: 1px 0;
|
|
||||||
background: $zebra;
|
|
||||||
|
|
||||||
&.ss-gridfield-last {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
border-right: 1px solid darken($zebra, 5%);
|
|
||||||
|
|
||||||
&.ss-gridfield-last {
|
&.ss-gridfield-last {
|
||||||
border-right: none;
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #FFF;
|
||||||
|
padding: 0;
|
||||||
|
border-right: 1px solid #85959C;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
/* Makes it appear as though the text sits over the boundary of the two <tr>'s in <thead> */
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
left: 20px;
|
||||||
|
top: -7px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
&.fieldgroup,&.fieldgroup-field {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
&.fieldgroup {
|
||||||
|
min-width: 200px; /* Not sure why IE think it needs this */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.extra,&.action {
|
||||||
|
background: $gf_colour_gradient_dark;
|
||||||
|
padding: 0;
|
||||||
|
cursor: default;
|
||||||
|
button,button:hover {
|
||||||
|
&.ss-ui-button {
|
||||||
|
margin-left: .9em;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.extra {
|
||||||
|
text-align: center;
|
||||||
|
@include gridfield-gradient-top;
|
||||||
|
span {
|
||||||
|
width: auto;
|
||||||
|
display: inline;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.action {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
&.first {
|
||||||
|
@include border-top-left-radius($gf_border_radius);
|
||||||
|
}
|
||||||
|
&.last {
|
||||||
|
@include border-top-right-radius($gf_border_radius);
|
||||||
|
}
|
||||||
|
button,button:hover {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: -.9em;
|
||||||
|
&.ss-gridfield-sort {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0;
|
||||||
|
color: #FFF;
|
||||||
|
width: 95%;
|
||||||
|
background: transparent;
|
||||||
|
border: 0 none;
|
||||||
|
@include box-shadow-none;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
&:hover {
|
||||||
|
color: #CCC !important; /* Not sure why IE think it needs this */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.extra button.ss-ui-button {
|
||||||
|
padding: .3em;
|
||||||
|
line-height: 1;
|
||||||
|
@include box-shadow-none;
|
||||||
|
position: relative;
|
||||||
|
top: -24px;
|
||||||
|
border: $gf_colour_gradient_light solid 10px;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
&.ss-gridfield-sort {
|
||||||
|
position: relative;
|
||||||
|
top: -24px;
|
||||||
|
padding: 2px;
|
||||||
|
width: 65%;
|
||||||
|
margin: 0 auto;
|
||||||
|
border: $gf_colour_gradient_light solid 10px;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
&:focus {
|
||||||
|
@include box-shadow-none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border-right: 1px solid $gf_colour_border;
|
||||||
|
padding: 10px;
|
||||||
|
&.bottom-all {
|
||||||
|
@include border-bottom-radius($gf_border_radius);
|
||||||
|
@include gridfield-gradient-bottom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
0
scss/newfile
Normal file
0
scss/newfile
Normal file
@ -1,25 +0,0 @@
|
|||||||
<% require css(sapphire/thirdparty/jquery-ui-themes/smoothness/jquery-ui.css) %>
|
|
||||||
<% require css(sapphire/css/GridField.css) %>
|
|
||||||
|
|
||||||
<div class="ss-gridfield ui-state-default" id="$Name">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<% control Headings %>
|
|
||||||
<th class="<% if FirstLast %>ss-gridfield-{$FirstLast} <% if IsSortable %> ss-gridfield-sortable<% end_if %> <% if IsSorted %>ss-gridfield-sorted ss-gridfield-{$SortedDirection}">
|
|
||||||
$Title <span class="ui-icon"></span></th>
|
|
||||||
<% end_control %>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<% control Items %>
|
|
||||||
<% include GridField_Item %>
|
|
||||||
<% end_control %>
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
<tfoot>
|
|
||||||
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
@ -1,29 +0,0 @@
|
|||||||
<% 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 %>
|
|
@ -1,29 +0,0 @@
|
|||||||
<% require css(sapphire/thirdparty/jquery-ui-themes/smoothness/jquery-ui.css) %>
|
|
||||||
<% require css(sapphire/css/GridField.css) %>
|
|
||||||
|
|
||||||
<div class="ss-gridfield ui-state-default" id="$Name">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<% control Headers %>
|
|
||||||
<th class="<% if FirstLast %>ss-gridfield-{$FirstLast}<% end_if %><% if IsSortable %> ss-gridfield-sortable<% end_if %><% if IsSorted %> ss-gridfield-sorted ss-gridfield-{$SortedDirection}<% end_if %>">
|
|
||||||
$Title <span class="ui-icon"></span></th>
|
|
||||||
<% end_control %>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<% control Items %>
|
|
||||||
<% include GridField_Item %>
|
|
||||||
<% end_control %>
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
<tfoot>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<% control Footers %>
|
|
||||||
$Render
|
|
||||||
<% end_control %>
|
|
||||||
|
|
||||||
</div>
|
|
5
templates/Includes/GridFieldFilter_Row.ss
Normal file
5
templates/Includes/GridFieldFilter_Row.ss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<tr>
|
||||||
|
<% control Fields %>
|
||||||
|
<th class="extra $FirstLast"><span>$Field</span></th>
|
||||||
|
<% end_control %>
|
||||||
|
</tr>
|
7
templates/Includes/GridFieldPaginator_Row.ss
Normal file
7
templates/Includes/GridFieldPaginator_Row.ss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<tr>
|
||||||
|
<td class="bottom-all" colspan="$Colspan">
|
||||||
|
<% control Fields %>
|
||||||
|
$Field
|
||||||
|
<% end_control %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
5
templates/Includes/GridFieldSortableHeader_Row.ss
Normal file
5
templates/Includes/GridFieldSortableHeader_Row.ss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<tr class="sortable-header">
|
||||||
|
<% control Fields %>
|
||||||
|
<th class="main"><span>$Field</span></th>
|
||||||
|
<% end_control %>
|
||||||
|
</tr>
|
@ -1,5 +1,12 @@
|
|||||||
<tr class="ss-gridfield-{$EvenOdd} $FirstLast">
|
<tr class="ss-gridfield-{$EvenOdd} $FirstLast">
|
||||||
<% control Fields %>
|
<% if $GridField.ExtraColumnsCount %>
|
||||||
<td <% if FirstLast %>class="ss-gridfield-{$FirstLast}"<% end_if %>>$Value</td>
|
<% control Fields %>
|
||||||
<% end_control %>
|
<td>$Value</td>
|
||||||
|
<% end_control %>
|
||||||
|
<td colspan="$GridField.ExtraColumnsCount" class="ss-gridfield-last"></td>
|
||||||
|
<% else %>
|
||||||
|
<% control Fields %>
|
||||||
|
<td <% if FirstLast %>class="ss-gridfield-{$FirstLast}"<% end_if %>>$Value</td>
|
||||||
|
<% end_control %>
|
||||||
|
<% end_if %>
|
||||||
</tr>
|
</tr>
|
@ -234,6 +234,17 @@ class RequestHandlingTest extends FunctionalTest {
|
|||||||
$this->assertContains('not allowed on form', $response->getBody());
|
$this->assertContains('not allowed on form', $response->getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testActionHandlingOnField() {
|
||||||
|
$data = array('action_actionOnField' => 1);
|
||||||
|
$response = $this->post('RequestHandlingFieldTest_Controller/TestForm', $data);
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertEquals('Test method on MyField', $response->getBody());
|
||||||
|
|
||||||
|
$data = array('action_actionNotAllowedOnField' => 1);
|
||||||
|
$response = $this->post('RequestHandlingFieldTest_Controller/TestForm', $data);
|
||||||
|
$this->assertEquals(404, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -544,3 +555,32 @@ class RequestHandlingTest_SubclassedFormField extends RequestHandlingTest_FormFi
|
|||||||
return "customSomething";
|
return "customSomething";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the test
|
||||||
|
*/
|
||||||
|
class RequestHandlingFieldTest_Controller extends Controller implements TestOnly {
|
||||||
|
|
||||||
|
function TestForm() {
|
||||||
|
return new Form($this, "TestForm", new FieldList(
|
||||||
|
new RequestHandlingTest_HandlingField("MyField")
|
||||||
|
), new FieldList(
|
||||||
|
new FormAction("myAction")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form field for the test
|
||||||
|
*/
|
||||||
|
class RequestHandlingTest_HandlingField extends FormField {
|
||||||
|
|
||||||
|
static $allowed_actions = array(
|
||||||
|
'actionOnField'
|
||||||
|
);
|
||||||
|
|
||||||
|
function actionOnField() {
|
||||||
|
return "Test method on $this->name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@ class FileTest extends SapphireTest {
|
|||||||
// Note: We can't use fixtures/setUp() for this, as we want to create the db record manually.
|
// Note: We can't use fixtures/setUp() for this, as we want to create the db record manually.
|
||||||
// Creating the folder is necessary to avoid having "Filename" overwritten by setName()/setRelativePath(),
|
// Creating the folder is necessary to avoid having "Filename" overwritten by setName()/setRelativePath(),
|
||||||
// because the parent folders don't exist in the database
|
// because the parent folders don't exist in the database
|
||||||
$folder = Folder::findOrMake('/FileTest/');
|
$folder = Folder::find_or_make('/FileTest/');
|
||||||
$testfilePath = 'assets/FileTest/CreateWithFilenameHasCorrectPath.txt'; // Important: No leading slash
|
$testfilePath = 'assets/FileTest/CreateWithFilenameHasCorrectPath.txt'; // Important: No leading slash
|
||||||
$fh = fopen(BASE_PATH . '/' . $testfilePath, "w");
|
$fh = fopen(BASE_PATH . '/' . $testfilePath, "w");
|
||||||
fwrite($fh, str_repeat('x',1000000));
|
fwrite($fh, str_repeat('x',1000000));
|
||||||
|
@ -33,7 +33,7 @@ class FolderTest extends SapphireTest {
|
|||||||
|
|
||||||
function testFindOrMake() {
|
function testFindOrMake() {
|
||||||
$path = '/FolderTest/testFindOrMake/';
|
$path = '/FolderTest/testFindOrMake/';
|
||||||
$folder = Folder::findOrMake($path);
|
$folder = Folder::find_or_make($path);
|
||||||
$this->assertEquals(ASSETS_DIR . $path,$folder->getRelativePath(),
|
$this->assertEquals(ASSETS_DIR . $path,$folder->getRelativePath(),
|
||||||
'Nested path information is correctly saved to database (with trailing slash)'
|
'Nested path information is correctly saved to database (with trailing slash)'
|
||||||
);
|
);
|
||||||
@ -44,13 +44,13 @@ class FolderTest extends SapphireTest {
|
|||||||
$this->assertEquals($parentFolder->ID, $folder->ParentID);
|
$this->assertEquals($parentFolder->ID, $folder->ParentID);
|
||||||
|
|
||||||
$path = '/FolderTest/testFindOrMake'; // no trailing slash
|
$path = '/FolderTest/testFindOrMake'; // no trailing slash
|
||||||
$folder = Folder::findOrMake($path);
|
$folder = Folder::find_or_make($path);
|
||||||
$this->assertEquals(ASSETS_DIR . $path . '/',$folder->getRelativePath(),
|
$this->assertEquals(ASSETS_DIR . $path . '/',$folder->getRelativePath(),
|
||||||
'Path information is correctly saved to database (without trailing slash)'
|
'Path information is correctly saved to database (without trailing slash)'
|
||||||
);
|
);
|
||||||
|
|
||||||
$path = '/assets/'; // relative to "assets/" folder, should produce "assets/assets/"
|
$path = '/assets/'; // relative to "assets/" folder, should produce "assets/assets/"
|
||||||
$folder = Folder::findOrMake($path);
|
$folder = Folder::find_or_make($path);
|
||||||
$this->assertEquals(ASSETS_DIR . $path,$folder->getRelativePath(),
|
$this->assertEquals(ASSETS_DIR . $path,$folder->getRelativePath(),
|
||||||
'A folder named "assets/" within "assets/" is allowed'
|
'A folder named "assets/" within "assets/" is allowed'
|
||||||
);
|
);
|
||||||
@ -126,7 +126,7 @@ class FolderTest extends SapphireTest {
|
|||||||
*/
|
*/
|
||||||
function testFindOrMakeFolderThenMove() {
|
function testFindOrMakeFolderThenMove() {
|
||||||
$folder1 = $this->objFromFixture('Folder', 'folder1');
|
$folder1 = $this->objFromFixture('Folder', 'folder1');
|
||||||
Folder::findOrMake($folder1->Filename);
|
Folder::find_or_make($folder1->Filename);
|
||||||
$folder2 = $this->objFromFixture('Folder', 'folder2');
|
$folder2 = $this->objFromFixture('Folder', 'folder2');
|
||||||
|
|
||||||
// set ParentID
|
// set ParentID
|
||||||
@ -182,7 +182,7 @@ class FolderTest extends SapphireTest {
|
|||||||
|
|
||||||
function testDeleteAlsoRemovesFilesystem() {
|
function testDeleteAlsoRemovesFilesystem() {
|
||||||
$path = '/FolderTest/DeleteAlsoRemovesFilesystemAndChildren';
|
$path = '/FolderTest/DeleteAlsoRemovesFilesystemAndChildren';
|
||||||
$folder = Folder::findOrMake($path);
|
$folder = Folder::find_or_make($path);
|
||||||
$this->assertFileExists(ASSETS_PATH . $path);
|
$this->assertFileExists(ASSETS_PATH . $path);
|
||||||
|
|
||||||
$folder->delete();
|
$folder->delete();
|
||||||
@ -193,8 +193,8 @@ class FolderTest extends SapphireTest {
|
|||||||
function testDeleteAlsoRemovesSubfoldersInDatabaseAndFilesystem() {
|
function testDeleteAlsoRemovesSubfoldersInDatabaseAndFilesystem() {
|
||||||
$path = '/FolderTest/DeleteAlsoRemovesSubfoldersInDatabaseAndFilesystem';
|
$path = '/FolderTest/DeleteAlsoRemovesSubfoldersInDatabaseAndFilesystem';
|
||||||
$subfolderPath = $path . '/subfolder';
|
$subfolderPath = $path . '/subfolder';
|
||||||
$folder = Folder::findOrMake($path);
|
$folder = Folder::find_or_make($path);
|
||||||
$subfolder = Folder::findOrMake($subfolderPath);
|
$subfolder = Folder::find_or_make($subfolderPath);
|
||||||
$subfolderID = $subfolder->ID;
|
$subfolderID = $subfolder->ID;
|
||||||
|
|
||||||
$folder->delete();
|
$folder->delete();
|
||||||
@ -206,7 +206,7 @@ class FolderTest extends SapphireTest {
|
|||||||
|
|
||||||
function testDeleteAlsoRemovesContainedFilesInDatabaseAndFilesystem() {
|
function testDeleteAlsoRemovesContainedFilesInDatabaseAndFilesystem() {
|
||||||
$path = '/FolderTest/DeleteAlsoRemovesContainedFilesInDatabaseAndFilesystem';
|
$path = '/FolderTest/DeleteAlsoRemovesContainedFilesInDatabaseAndFilesystem';
|
||||||
$folder = Folder::findOrMake($path);
|
$folder = Folder::find_or_make($path);
|
||||||
|
|
||||||
$file = $this->objFromFixture('File', 'gif');
|
$file = $this->objFromFixture('File', 'gif');
|
||||||
$file->ParentID = $folder->ID;
|
$file->ParentID = $folder->ID;
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a functional test for GridFieldFunctionalTest
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class GridFieldFunctionalTest extends FunctionalTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
static $fixture_file = 'sapphire/tests/forms/GridFieldTest.yml';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $extraDataObjects = array(
|
|
||||||
'GridFieldTest_Person',
|
|
||||||
);
|
|
||||||
|
|
||||||
public function testAddToForm() {
|
|
||||||
$firstPerson = $this->objFromFixture('GridFieldTest_Person', 'first');
|
|
||||||
$response = $this->get("GridFieldFunctionalTest_Controller/");
|
|
||||||
$this->assertContains($firstPerson->Name, $response->getBody());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GridFieldFunctionalTest_Controller extends Controller {
|
|
||||||
|
|
||||||
protected $template = 'BlankPage';
|
|
||||||
|
|
||||||
function Link($action = null) {
|
|
||||||
return Controller::join_links('GridFieldFunctionalTest_Controller', $action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function index() {
|
|
||||||
$grid = new GridField('testgrid');
|
|
||||||
$dataSource = DataList::create("GridFieldTest_Person")->sort("Name");
|
|
||||||
$grid->setList($dataSource);
|
|
||||||
$form = new Form($this, 'gridform', new FieldList($grid), new FieldList(new FormAction('rerender', 'rerender')));
|
|
||||||
return array('Form'=>$form);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package sapphire
|
|
||||||
* @subpackage tests
|
|
||||||
*/
|
|
||||||
class GridFieldPresenterTest extends SapphireTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
static $fixture_file = 'sapphire/tests/forms/GridFieldTest.yml';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $extraDataObjects = array(
|
|
||||||
'GridFieldTest_Person',
|
|
||||||
);
|
|
||||||
|
|
||||||
public function testGetInstance() {
|
|
||||||
$this->assertTrue(new GridFieldPresenter instanceof GridFieldPresenter, 'Trying to find an instance of GridFieldPresenter');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHeaders() {
|
|
||||||
$presenter = new GridFieldPresenter();
|
|
||||||
$grid = new GridField('testgrid', 'testgrid', new DataList('GridFieldTest_Person'));
|
|
||||||
$presenter->setGridField($grid);
|
|
||||||
$headers = $presenter->Headers()->first();
|
|
||||||
|
|
||||||
$this->assertEquals(1, count($headers));
|
|
||||||
$this->assertEquals('Name', $headers->Name );
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItemsReturnCorrectNumberOfItems() {
|
|
||||||
$presenter = new GridFieldPresenter();
|
|
||||||
$grid = new GridField('testgrid', 'testgrid', new DataList('GridFieldTest_Person'));
|
|
||||||
$presenter->setGridField($grid);
|
|
||||||
$this->assertEquals(2, $presenter->Items()->count());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSorting(){
|
|
||||||
$presenter = new GridFieldPresenter();
|
|
||||||
$GridField = new GridField('testgrid', 'testgrid', new DataList('GridFieldTest_Person'));
|
|
||||||
$presenter->setGridField($GridField);
|
|
||||||
$presenter->sort('Name','desc');
|
|
||||||
$data = $presenter->Items()->map('ID','Name');
|
|
||||||
$this->assertEquals(array(
|
|
||||||
$this->idFromFixture('GridFieldTest_Person', 'second') => 'Second Person',
|
|
||||||
$this->idFromFixture('GridFieldTest_Person', 'first') => 'First Person'
|
|
||||||
), $data);
|
|
||||||
$presenter->sort('Name','asc');
|
|
||||||
$data = $presenter->Items()->map('ID','Name');
|
|
||||||
$this->assertEquals(array(
|
|
||||||
$this->idFromFixture('GridFieldTest_Person', 'first') => 'First Person',
|
|
||||||
$this->idFromFixture('GridFieldTest_Person', 'second') => 'Second Person'
|
|
||||||
), $data);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a Unittest class for GridFieldTest
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class GridFieldTest extends SapphireTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
static $fixture_file = 'sapphire/tests/forms/GridFieldTest.yml';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $extraDataObjects = array(
|
|
||||||
'GridFieldTest_Person',
|
|
||||||
);
|
|
||||||
|
|
||||||
public function testGetInstance() {
|
|
||||||
$this->assertTrue(new GridField('Testgrid') instanceof FormField, 'GridField should be a FormField');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetDataSource() {
|
|
||||||
$grid = new GridField('Testgrid');
|
|
||||||
$source = new ArrayList();
|
|
||||||
$grid->setList($source);
|
|
||||||
$this->assertEquals($source, $grid->getList());
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSetEmptyDataPresenter() {
|
|
||||||
$this->setExpectedException('InvalidArgumentException');
|
|
||||||
$grid = new GridField('Testgrid');
|
|
||||||
$grid->setPresenter('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSetNonExistingDataPresenter() {
|
|
||||||
$this->setExpectedException('InvalidArgumentException');
|
|
||||||
$grid = new GridField('Testgrid');
|
|
||||||
$grid->setPresenter('ifThisClassExistsIWouldBeSurprised');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSetDataPresenterWithDataObject() {
|
|
||||||
$this->setExpectedException('InvalidArgumentException');
|
|
||||||
$grid = new GridField('Testgrid');
|
|
||||||
$grid->setPresenter('DataObject');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSetDataPresenter() {
|
|
||||||
$grid = new GridField('Testgrid');
|
|
||||||
$grid->setPresenter('GridFieldPresenter');
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSetDataclass() {
|
|
||||||
$grid = new GridField('Testgrid');
|
|
||||||
$grid->setModelClass('SiteTree');
|
|
||||||
$this->assertEquals('SiteTree', $grid->getModelClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function testFieldHolderWithoutDataSource() {
|
|
||||||
$this->setExpectedException('LogicException');
|
|
||||||
$grid = new GridField('Testgrid');
|
|
||||||
$this->assertNotNull($grid->FieldHolder());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is better tested in the GridFieldFunctionalTest
|
|
||||||
*
|
|
||||||
* @see GridFieldFunctionalTest
|
|
||||||
*/
|
|
||||||
function testFieldHolder() {
|
|
||||||
$grid = new GridField('Testgrid');
|
|
||||||
$grid->setList(new DataList('GridFieldTest_Person'));
|
|
||||||
$this->assertNotNull($grid->FieldHolder());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GridFieldTest_Person extends Dataobject implements TestOnly {
|
|
||||||
|
|
||||||
public static $db = array(
|
|
||||||
'Name' => 'Varchar'
|
|
||||||
);
|
|
||||||
|
|
||||||
public static $summary_fields = array(
|
|
||||||
'Name',
|
|
||||||
'ID'
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
GridFieldTest_Person:
|
|
||||||
first:
|
|
||||||
Name: First Person
|
|
||||||
second:
|
|
||||||
Name: Second Person
|
|
Loading…
Reference in New Issue
Block a user