mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #172 from silverstripe-scienceninjas/story/ssf-104
API CHANGE Security admin supports adding, removing and searching for members by relations via gridfield
This commit is contained in:
commit
a4f1fbaac5
14
admin/code/SecurityAdmin.php
Normal file → Executable file
14
admin/code/SecurityAdmin.php
Normal file → Executable file
@ -35,11 +35,10 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
|
||||
|
||||
public function init() {
|
||||
parent::init();
|
||||
|
||||
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/SecurityAdmin.js');
|
||||
}
|
||||
|
||||
function getEditForm($id = null) {
|
||||
public function getEditForm($id = null, $fields = null) {
|
||||
// TODO Duplicate record fetching (see parent implementation)
|
||||
if(!$id) $id = $this->currentPageID();
|
||||
$form = parent::getEditForm($id);
|
||||
@ -107,12 +106,9 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
|
||||
* @return FieldList
|
||||
*/
|
||||
function RootForm() {
|
||||
$memberList = new MemberTableField(
|
||||
$this,
|
||||
"Members"
|
||||
);
|
||||
// unset 'inlineadd' permission, we don't want inline addition
|
||||
$memberList->setPermissions(array('edit', 'delete', 'add'));
|
||||
$config = new GridFieldConfig_Base(25);
|
||||
$config->addComponent(new GridFieldPopupForms($this, 'RootForm'));
|
||||
$memberList = new GridField('Members', 'All members', DataList::create('Member'), $config);
|
||||
|
||||
$fields = new FieldList(
|
||||
$root = new TabSet(
|
||||
@ -132,7 +128,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
|
||||
new LiteralField(
|
||||
'GroupImportFormIframe',
|
||||
sprintf(
|
||||
'<iframe src="%s" id="GroupImportFormIframe" width="100%%" height="400px" border="0"></iframe>',
|
||||
'<iframe src="%s" id="GroupImportFormIframe" width="100%" height="400px" border="0"></iframe>',
|
||||
$this->Link('groupimport')
|
||||
)
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
/** 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? */
|
||||
.cms fieldset.ss-gridfield > div { margin-bottom: 35px; }
|
||||
.cms table.ss-gridfield.field { box-shadow: none; padding: 0; margin: 20px 0 0 0; border-collapse: separate; border-bottom: 0 none; }
|
||||
.cms table.ss-gridfield.field thead { color: #1d2224; background: transparent; }
|
||||
.cms table.ss-gridfield.field tbody { background: #FFF; }
|
||||
@ -11,7 +12,7 @@
|
||||
.cms table.ss-gridfield.field tr:first-child { background: transparent; }
|
||||
.cms table.ss-gridfield.field tr.ss-gridfield-even { background: #f2f9fd; }
|
||||
.cms table.ss-gridfield.field tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; }
|
||||
.cms table.ss-gridfield.field 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> */ }
|
||||
.cms table.ss-gridfield.field tr th { font-weight: bold; font-size: 12px; color: #FFF; padding: 0; border-right: 1px solid #85959C; height: 20px; white-space: nowrap; /* Makes it appear as though the text sits over the boundary of the two <tr>'s in <thead> */ }
|
||||
.cms table.ss-gridfield.field tr th span { display: block; position: relative; left: 20px; top: -7px; width: 100%; }
|
||||
.cms table.ss-gridfield.field tr th div.fieldgroup, .cms table.ss-gridfield.field tr th div.fieldgroup-field { width: auto; }
|
||||
.cms table.ss-gridfield.field tr th div.fieldgroup { min-width: 200px; }
|
||||
@ -23,7 +24,7 @@
|
||||
.cms table.ss-gridfield.field 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.field 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.field tr th button, .cms table.ss-gridfield.field tr th button:hover { font-size: 12px; margin-left: -0.9em; border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
||||
.cms table.ss-gridfield.field tr th button.ss-gridfield-sort, .cms table.ss-gridfield.field 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.field tr th button.ss-gridfield-sort, .cms table.ss-gridfield.field tr th button:hover.ss-gridfield-sort { text-align: left; padding: 0; color: #FFF; min-width: 100px; background: transparent; border: 0 none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; text-shadow: none; }
|
||||
.cms table.ss-gridfield.field tr th button:hover { color: #CCC !important; /* Not sure why IE think it needs this */ }
|
||||
.cms table.ss-gridfield.field 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.field 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; }
|
||||
|
@ -405,7 +405,7 @@ class Folder extends File {
|
||||
$config->addComponent(new GridFieldPaginator(10));
|
||||
$config->addComponent(new GridFieldAction_Delete());
|
||||
$config->addComponent(new GridFieldAction_Edit());
|
||||
$config->addComponent($gridFieldForm = new GridFieldPopupForms());
|
||||
$config->addComponent($gridFieldForm = new GridFieldPopupForms(Controller::curr(), 'EditForm'));
|
||||
$gridFieldForm->setTemplate('CMSGridFieldPopupForms');
|
||||
$files = DataList::create('File')->filter('ParentID', $this->ID)->exclude('ClassName', 'Folder');
|
||||
$gridField = new GridField('File','Files', $files, $config);
|
||||
|
@ -511,7 +511,11 @@ class GridField extends FormField {
|
||||
|
||||
$actionName = $stateChange['actionName'];
|
||||
$args = isset($stateChange['args']) ? $stateChange['args'] : array();
|
||||
$grid->handleAction($actionName, $args, $data);
|
||||
$html = $grid->handleAction($actionName, $args, $data);
|
||||
|
||||
if($html) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
switch($request->getHeader('X-Get-Fragment')) {
|
||||
case 'CurrentField':
|
||||
@ -539,13 +543,13 @@ class GridField extends FormField {
|
||||
*/
|
||||
public function handleAction($actionName, $args, $data) {
|
||||
$actionName = strtolower($actionName);
|
||||
foreach($this->components as $item) {
|
||||
if(!($item instanceof GridField_ActionProvider)) {
|
||||
foreach($this->components as $component) {
|
||||
if(!($component instanceof GridField_ActionProvider)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(in_array($actionName, array_map('strtolower', $item->getActions($this)))) {
|
||||
return $item->handleAction($this, $actionName, $args, $data);
|
||||
if(in_array($actionName, array_map('strtolower', $component->getActions($this)))) {
|
||||
return $component->handleAction($this, $actionName, $args, $data);
|
||||
}
|
||||
}
|
||||
throw new InvalidArgumentException("Can't handle action '$actionName'");
|
||||
@ -564,8 +568,6 @@ class GridField extends FormField {
|
||||
$this->request = $request;
|
||||
$this->setModel($model);
|
||||
|
||||
///
|
||||
|
||||
foreach($this->components as $component) {
|
||||
if(!($component instanceof GridField_URLHandler)) {
|
||||
continue;
|
||||
|
105
forms/gridfield/GridFieldConfig.php
Normal file → Executable file
105
forms/gridfield/GridFieldConfig.php
Normal file → Executable file
@ -19,24 +19,6 @@ class GridFieldConfig {
|
||||
*/
|
||||
protected $components = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $checkboxes = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $affectors = array();
|
||||
|
||||
/**
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $decorators = array();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ -59,30 +41,69 @@ class GridFieldConfig {
|
||||
}
|
||||
return $this->components;
|
||||
}
|
||||
|
||||
public function setCheckboxes($row=0){
|
||||
$this->checkboxes = $row;
|
||||
return $this;
|
||||
}
|
||||
|
||||
class GridFieldConfig_Base extends GridFieldConfig {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param int $itemsPerPage - How many items per page should show up per page
|
||||
* @return GridFieldConfig_Base
|
||||
*/
|
||||
public static function create($itemsPerPage=25){
|
||||
return new GridFieldConfig_Base($itemsPerPage=25);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param int $itemsPerPage - How many items per page should show up
|
||||
*/
|
||||
public function __construct($itemsPerPage=25) {
|
||||
$this->addComponent(new GridFieldSortableHeader());
|
||||
$this->addComponent(new GridFieldDefaultColumns());
|
||||
$this->addComponent(new GridFieldAction_Edit());
|
||||
$this->addComponent(new GridFieldPaginator($itemsPerPage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This GridFieldConfig bundles a common set of componentes used for displaying
|
||||
* a gridfield with:
|
||||
*
|
||||
* - Relation adding
|
||||
* - Sortable header
|
||||
* - Default columns
|
||||
* - Edit links on every item
|
||||
* - Action for removing relationship
|
||||
* - Paginator
|
||||
*
|
||||
*/
|
||||
class GridFieldConfig_ManyManyEditor extends GridFieldConfig {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $fieldToSearch - Which field on the object should be searched for
|
||||
* @param bool $autoSuggest - Show a jquery.ui.autosuggest dropdown field
|
||||
* @param int $itemsPerPage - How many items per page should show up
|
||||
* @return GridFieldConfig_ManyManyEditor
|
||||
*/
|
||||
public static function create($fieldToSearch, $autoSuggest=true, $itemsPerPage=25){
|
||||
return new GridFieldConfig_ManyManyEditor($fieldToSearch, $autoSuggest=true, $itemsPerPage=25);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $fieldToSearch - Which field on the object should be searched for
|
||||
* @param bool $autoSuggest - Show a jquery.ui.autosuggest dropdown field
|
||||
* @param int $itemsPerPage - How many items per page should show up
|
||||
*/
|
||||
public function __construct($fieldToSearch, $autoSuggest=true, $itemsPerPage=25) {
|
||||
$this->addComponent(new GridFieldFilter());
|
||||
$this->addComponent(new GridFieldRelationAdd($fieldToSearch, $autoSuggest));
|
||||
$this->addComponent(new GridFieldSortableHeader());
|
||||
$this->addComponent(new GridFieldDefaultColumns());
|
||||
$this->addComponent(new GridFieldAction_Edit());
|
||||
$this->addComponent(new GridFieldRelationDelete());
|
||||
$this->addComponent(new GridFieldPaginator($itemsPerPage));
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,6 @@
|
||||
*/
|
||||
class GridFieldFilter implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $location = 'head';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
|
67
forms/gridfield/GridFieldPaginator.php
Normal file → Executable file
67
forms/gridfield/GridFieldPaginator.php
Normal file → Executable file
@ -1,35 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* GridFieldPaginator decorates the GridFieldPresenter with the possibility to
|
||||
* paginate the GridField.
|
||||
*
|
||||
* @see GridField
|
||||
* GridFieldPaginator paginates the gridfields list and adds controlls to the
|
||||
* bottom of the gridfield.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {
|
||||
|
||||
/**
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $currentPage = 1;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $itemsPerPage = 25;
|
||||
|
||||
/**
|
||||
* Which template to use for rendering
|
||||
*
|
||||
* @var string $itemClass
|
||||
* @var string
|
||||
*/
|
||||
protected $itemClass = 'GridFieldPaginator_Row';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param int $itemsPerPage - How many items should be displayed per page
|
||||
*/
|
||||
public function __construct($itemsPerPage=25) {
|
||||
$this->itemsPerPage = $itemsPerPage;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @return array
|
||||
*/
|
||||
public function getActions($gridField) {
|
||||
return array('paginate');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param string $actionName
|
||||
* @param string $arguments
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
|
||||
if($actionName !== 'paginate') {
|
||||
return;
|
||||
@ -38,17 +60,12 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param SS_List $dataList
|
||||
* @return SS_List
|
||||
*/
|
||||
public function getManipulatedData(GridField $gridField, SS_List $dataList) {
|
||||
if(!$this->getListPaginatable($dataList)) {
|
||||
return $dataList;
|
||||
@ -60,6 +77,11 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
|
||||
return $dataList->getRange((int)$startRow, (int)$this->itemsPerPage);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @return array
|
||||
*/
|
||||
public function getHTMLFragments($gridField) {
|
||||
Requirements::javascript(SAPPHIRE_DIR.'/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
|
||||
Requirements::javascript(SAPPHIRE_DIR.'/javascript/GridField.js');
|
||||
@ -87,4 +109,15 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
|
||||
'footer' => $forTemplate->renderWith('GridFieldPaginator_Row', array('Colspan'=>count($gridField->getColumns()))),
|
||||
);
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
|
106
forms/gridfield/GridFieldPopupForms.php
Normal file → Executable file
106
forms/gridfield/GridFieldPopupForms.php
Normal file → Executable file
@ -13,16 +13,51 @@ class GridFieldPopupForms implements GridField_URLHandler {
|
||||
* @var String
|
||||
*/
|
||||
protected $template = 'GridFieldItemEditView';
|
||||
|
||||
/**
|
||||
*
|
||||
* @var Controller
|
||||
*/
|
||||
protected $popupController;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $popupFormName;
|
||||
|
||||
function getURLHandlers($gridField) {
|
||||
return array(
|
||||
'item/$ID' => 'handleItem',
|
||||
'autocomplete' => 'handleAutocomplete',
|
||||
);
|
||||
}
|
||||
|
||||
function handleItem($gridField, $request) {
|
||||
/**
|
||||
* Create a popup component. The two arguments will specify how the popup form's HTML and
|
||||
* behaviour is created. The given controller will be customised, putting the edit form into the
|
||||
* template with the given name.
|
||||
*
|
||||
* The arguments are experimental API's to support partial content to be passed back to whatever
|
||||
* controller who wants to display the getCMSFields
|
||||
*
|
||||
* @param Controller $popupController The controller object that will be used to render the pop-up forms
|
||||
* @param string $popupFormName The name of the edit form to place into the pop-up form
|
||||
*/
|
||||
public function __construct($popupController, $popupFormName) {
|
||||
$this->popupController = $popupController;
|
||||
$this->popupFormName = $popupFormName;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param type $gridField
|
||||
* @param type $request
|
||||
* @return GridFieldPopupForm_ItemRequest
|
||||
*/
|
||||
public function handleItem($gridField, $request) {
|
||||
$record = $gridField->getList()->byId($request->param("ID"));
|
||||
$handler = new GridFieldPopupForm_ItemRequest($gridField, $this, $record);
|
||||
$handler = new GridFieldPopupForm_ItemRequest($gridField, $this, $record, $this->popupController, $this->popupFormName);
|
||||
$handler->setTemplate($this->template);
|
||||
return $handler;
|
||||
}
|
||||
@ -44,12 +79,36 @@ class GridFieldPopupForms implements GridField_URLHandler {
|
||||
|
||||
class GridFieldPopupForm_ItemRequest extends RequestHandler {
|
||||
|
||||
/**
|
||||
*
|
||||
* @var GridField
|
||||
*/
|
||||
protected $gridField;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var GridField_URLHandler
|
||||
*/
|
||||
protected $component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var DataObject
|
||||
*/
|
||||
protected $record;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var Controller
|
||||
*/
|
||||
protected $popupController;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $popupFormName;
|
||||
|
||||
/**
|
||||
* @var String
|
||||
*/
|
||||
@ -57,45 +116,66 @@ class GridFieldPopupForm_ItemRequest extends RequestHandler {
|
||||
|
||||
static $url_handlers = array(
|
||||
'$Action!' => '$Action',
|
||||
'' => 'index',
|
||||
'' => 'edit',
|
||||
);
|
||||
|
||||
function __construct($gridField, $component, $record) {
|
||||
/**
|
||||
*
|
||||
* @param GridFIeld $gridField
|
||||
* @param GridField_URLHandler $component
|
||||
* @param DataObject $record
|
||||
* @param Controller $popupController
|
||||
* @param string $popupFormName
|
||||
*/
|
||||
public function __construct($gridField, $component, $record, $popupController, $popupFormName) {
|
||||
$this->gridField = $gridField;
|
||||
$this->component = $gridField;
|
||||
$this->record = $record;
|
||||
|
||||
$this->popupController = $popupController;
|
||||
$this->popupFormName = $popupFormName;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function Link($action = null) {
|
||||
public function Link($action = null) {
|
||||
return Controller::join_links($this->gridField->Link('item'), $this->record->ID, $action);
|
||||
}
|
||||
|
||||
function edit($request) {
|
||||
$controller = $this->gridField->getForm()->Controller();
|
||||
|
||||
$controller = $this->popupController;
|
||||
|
||||
$return = $this->customise(array(
|
||||
'Backlink' => $controller->Link(),
|
||||
'Backlink' => $this->gridField->getForm()->Controller()->Link(),
|
||||
'ItemEditForm' => $this->ItemEditForm($this->gridField, $request),
|
||||
))->renderWith($this->template);
|
||||
|
||||
if($controller->isAjax()) {
|
||||
return $return;
|
||||
} else {
|
||||
|
||||
// If not requested by ajax, we need to render it within the controller context+template
|
||||
return $controller->customise(array(
|
||||
$this->gridField->getForm()->Name() => $return,
|
||||
$this->popupFormName => $return,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an item edit form. The arguments to getCMSFields() are the popupController and
|
||||
* popupFormName, however this is an experimental API and may change.
|
||||
*
|
||||
* In the future, we will probably need to come up with a tigher object representing a partially
|
||||
* complete controller with gaps for extra functionality. This, for example, would be a better way
|
||||
* of letting Security/login put its log-in form inside a UI specified elsewhere.
|
||||
*
|
||||
* @return Form
|
||||
*/
|
||||
function ItemEditForm() {
|
||||
$request = $this->gridField->getForm()->Controller()->getRequest();
|
||||
$request = $this->popupController->getRequest();
|
||||
$form = new Form(
|
||||
$this,
|
||||
'ItemEditForm',
|
||||
$this->record->getCMSFields(),
|
||||
// WARNING: The arguments passed here are a little arbitrary. This API will need cleanup
|
||||
$this->record->getCMSFields($this->popupController, $this->popupFormName),
|
||||
new FieldList(
|
||||
$saveAction = new FormAction('doSave', _t('GridFieldDetailsForm.Save', 'Save'))
|
||||
)
|
||||
@ -124,7 +204,7 @@ class GridFieldPopupForm_ItemRequest extends RequestHandler {
|
||||
|
||||
$form->sessionMessage($message, 'good');
|
||||
|
||||
return $this->gridField->getForm()->Controller()->redirectBack();
|
||||
return $this->popupController->redirectBack();
|
||||
}
|
||||
|
||||
/**
|
||||
|
198
forms/gridfield/GridFieldRelationAdd.php
Executable file
198
forms/gridfield/GridFieldRelationAdd.php
Executable file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* A GridFieldRelationAdd is responsible for adding objects to another objects
|
||||
* has_many and many_many relation. It will not attach duplicate objects.
|
||||
*
|
||||
* It augments a GridField with fields above the gridfield to search and add
|
||||
* objects to whatever the SS_List passed into the gridfield.
|
||||
*
|
||||
* If the object is set to use autosuggestion it will include jQuery UI
|
||||
* autosuggestion field that searches for current objects that isn't already
|
||||
* attached to the list.
|
||||
*/
|
||||
class GridFieldRelationAdd implements GridField_HTMLProvider, GridField_ActionProvider, GridField_DataManipulator, GridField_URLHandler {
|
||||
|
||||
/**
|
||||
* Which template to use for rendering
|
||||
*
|
||||
* @var string $itemClass
|
||||
*/
|
||||
protected $itemClass = 'GridFieldRelationAdd';
|
||||
|
||||
/**
|
||||
* Which column that should be used for doing a StartsWith search
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldToSearch = '';
|
||||
|
||||
/**
|
||||
* Use the jQuery.ui.autosuggestion plugin
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $useAutoSuggestion = true;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $fieldToSearch which field on the object in the list should be search
|
||||
* @param bool $autoSuggestion - if you would like to use the javascript autosuggest feature
|
||||
*/
|
||||
public function __construct($fieldToSearch, $autoSuggestion=true) {
|
||||
$this->fieldToSearch = $fieldToSearch;
|
||||
$this->useAutoSuggestion = $autoSuggestion;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @return string - HTML
|
||||
*/
|
||||
public function getHTMLFragments($gridField) {
|
||||
$searchState = $gridField->State->GridFieldSearchRelation;
|
||||
|
||||
|
||||
if($this->useAutoSuggestion){
|
||||
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
|
||||
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
|
||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
|
||||
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
|
||||
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/jquery-ui.js');
|
||||
Requirements::javascript(SAPPHIRE_DIR . "/javascript/GridFieldSearch.js");
|
||||
}
|
||||
|
||||
$forTemplate = new ArrayData(array());
|
||||
$forTemplate->Fields = new ArrayList();
|
||||
|
||||
$value = $this->findSingleEntry($gridField, $this->fieldToSearch, $searchState, $gridField->getList()->dataClass);
|
||||
$searchField = new TextField('gridfield_relationsearch', 'Auto Suggest Search field', $value);
|
||||
// Apparently the data-* needs to be double qouted for the jQuery.meta data plugin
|
||||
//
|
||||
$searchField->setAttribute('data-search-url', '\''.Controller::join_links($gridField->Link('search').'\''));
|
||||
|
||||
$findAction = new GridField_Action($gridField, 'gridfield_relationfind', 'Find', 'find', 'find');
|
||||
$addAction = new GridField_Action($gridField, 'gridfield_relationadd', 'Add Relation', 'addto', 'addto');
|
||||
|
||||
// If an object is not found, disable the action
|
||||
if(!is_int($gridField->State->GridFieldAddRelation)) {
|
||||
$addAction->setReadonly(true);
|
||||
}
|
||||
|
||||
$forTemplate->Fields->push($searchField);
|
||||
$forTemplate->Fields->push($findAction);
|
||||
$forTemplate->Fields->push($addAction);
|
||||
return array('before' => $forTemplate->renderWith($this->itemClass));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @return array
|
||||
*/
|
||||
public function getActions($gridField) {
|
||||
return array('addto', 'find');
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulate the state to either add a new relation, or doing a small search
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param string $actionName
|
||||
* @param string $arguments
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
|
||||
switch($actionName) {
|
||||
case 'addto':
|
||||
if(isset($data['relationID']) && $data['relationID']){
|
||||
$gridField->State->GridFieldAddRelation = $data['relationID'];
|
||||
}
|
||||
$gridField->State->GridFieldSearchRelation = '';
|
||||
break;
|
||||
case 'find' && isset($data['autosuggest_search']):
|
||||
$gridField->State->GridFieldSearchRelation = $data['autosuggest_search'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If an object ID is set, add the object to the list
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param SS_List $dataList
|
||||
* @return SS_List
|
||||
*/
|
||||
public function getManipulatedData(GridField $gridField, SS_List $dataList) {
|
||||
if(!$gridField->State->GridFieldAddRelation) {
|
||||
return $dataList;
|
||||
}
|
||||
$objectID = Convert::raw2sql($gridField->State->GridFieldAddRelation);
|
||||
if($objectID) {
|
||||
$object = DataObject::get_by_id($dataList->dataclass(), $objectID);
|
||||
if($object) {
|
||||
$dataList->add($object);
|
||||
}
|
||||
}
|
||||
$gridField->State->GridFieldAddRelation = null;
|
||||
return $dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @return array
|
||||
*/
|
||||
public function getURLHandlers($gridField) {
|
||||
return array(
|
||||
'search/$ID' => 'doSearch',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a json array of a search results that can be used by for example Jquery.ui.autosuggestion
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param SS_HTTPRequest $request
|
||||
*/
|
||||
public function doSearch($gridField, $request) {
|
||||
$allList = DataList::create($gridField->getList()->dataClass());
|
||||
$results = $allList->subtract($gridField->getList())->filter($this->fieldToSearch.':StartsWith',$request->param('ID'));
|
||||
$results->sort($this->fieldToSearch, 'ASC');
|
||||
|
||||
$json = array();
|
||||
foreach($results as $result) {
|
||||
$json[$result->ID] = $result->{$this->fieldToSearch};
|
||||
}
|
||||
return Convert::array2json($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will provide a StartsWith search that only returns a value if we are
|
||||
* matching ONE object only. We wouldn't want to attach used any object to
|
||||
* the list.
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param string $field
|
||||
* @param string $searchTerm
|
||||
* @param string $dataclass
|
||||
* @return string
|
||||
*/
|
||||
protected function findSingleEntry($gridField, $field, $searchTerm, $dataclass) {
|
||||
$fullList = DataList::create($dataclass);
|
||||
$searchTerm = Convert::raw2sql($searchTerm);
|
||||
if(!$searchTerm) {
|
||||
return;
|
||||
}
|
||||
$existingList = clone $gridField->getList();
|
||||
$searchResults = $fullList->subtract($existingList->limit(0))->filter($field.':StartsWith', $searchTerm);
|
||||
|
||||
// If more than one, skip
|
||||
if($searchResults->count() != 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$gridField->State->GridFieldAddRelation = $searchResults->first()->ID;
|
||||
return $searchResults->first()->$field;
|
||||
}
|
||||
}
|
99
forms/gridfield/GridFieldRelationDelete.php
Normal file
99
forms/gridfield/GridFieldRelationDelete.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* GridFieldRelationDelete
|
||||
*
|
||||
*/
|
||||
class GridFieldRelationDelete implements GridField_ColumnProvider, GridField_ActionProvider {
|
||||
|
||||
/**
|
||||
* Add a column 'UnlinkRelation'
|
||||
*
|
||||
* @param type $gridField
|
||||
* @param array $columns
|
||||
*/
|
||||
public function augmentColumns($gridField, &$columns) {
|
||||
$columns[] = 'UnlinkRelation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any special attributes that will be used for FormField::createTag()
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param DataObject $record
|
||||
* @param string $columnName
|
||||
* @return array
|
||||
*/
|
||||
public function getColumnAttributes($gridField, $record, $columnName) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't add an title
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param string $columnName
|
||||
* @return array
|
||||
*/
|
||||
public function getColumnMetadata($gridField, $columnName) {
|
||||
if($columnName == 'UnlinkRelation') {
|
||||
return array('title' => '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Which columns are handled by this component
|
||||
*
|
||||
* @param type $gridField
|
||||
* @return type
|
||||
*/
|
||||
public function getColumnsHandled($gridField) {
|
||||
return array('UnlinkRelation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Which GridField actions are this component handling
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @return array
|
||||
*/
|
||||
public function getActions($gridField) {
|
||||
return array('unlinkrelation');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param DataObject $record
|
||||
* @param string $columnName
|
||||
* @return string - the HTML for the column
|
||||
*/
|
||||
public function getColumnContent($gridField, $record, $columnName) {
|
||||
$field = new GridField_Action(
|
||||
$gridField,
|
||||
'UnlinkRelation'.$record->ID,
|
||||
_t('GridAction.UnlinkRelation', "Unlink"),
|
||||
"unlinkrelation",
|
||||
array('RecordID' => $record->ID)
|
||||
);
|
||||
$output = $field->Field();
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the actions and apply any changes to the GridField
|
||||
*
|
||||
* @param GridField $gridField
|
||||
* @param string $actionName
|
||||
* @param mixed $arguments
|
||||
* @param array $data - form data
|
||||
* @return void
|
||||
*/
|
||||
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
|
||||
$id = $arguments['RecordID'];
|
||||
$item = $gridField->getList()->byID($id);
|
||||
if(!$item) return;
|
||||
if($actionName == 'unlinkrelation') {
|
||||
$gridField->getList()->remove($item);
|
||||
}
|
||||
}
|
||||
}
|
36
javascript/GridFieldSearch.js
Normal file
36
javascript/GridFieldSearch.js
Normal file
@ -0,0 +1,36 @@
|
||||
jQuery(function($){
|
||||
|
||||
$(document).delegate("#gridfield_relationsearch", "focus", function (event) {
|
||||
$(this).autocomplete({
|
||||
source: function(request, response){
|
||||
var searchField = $(this.element);
|
||||
var form = $(this.element).closest("form");
|
||||
// Due to some very weird behaviout of jquery.metadata, the url have to be double quoted
|
||||
var suggestionUrl = $(searchField).attr('data-search-url').substr(1,$(searchField).attr('data-search-url').length-2);
|
||||
$.ajax({
|
||||
headers: {
|
||||
"X-Get-Fragment" : 'Partial'
|
||||
},
|
||||
type: "GET",
|
||||
url: suggestionUrl+'/'+request.term,
|
||||
data: form.serialize()+'&'+escape(searchField.attr('name'))+'='+escape(searchField.val()),
|
||||
success: function(data) {
|
||||
response( $.map(JSON.parse(data), function( name, id ) {
|
||||
return { label: name, value: name, id: id }
|
||||
}));
|
||||
},
|
||||
error: function(e) {
|
||||
alert(ss.i18n._t('GRIDFIELD.ERRORINTRANSACTION', 'An error occured while fetching data from the server\n Please try again later.'));
|
||||
}
|
||||
});
|
||||
},
|
||||
select: function(event, ui) {
|
||||
$(this).closest("fieldset.ss-gridfield").find("#action_gridfield_relationfind").replaceWith(
|
||||
'<input type="hidden" name="relationID" value="'+ui.item.id+'" id="relationID"/>'
|
||||
);
|
||||
$(this).closest("fieldset.ss-gridfield").find("#action_gridfield_relationadd").removeAttr('disabled');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -45,6 +45,9 @@ $gf_border_radius: 7px;
|
||||
}
|
||||
|
||||
.cms {
|
||||
fieldset.ss-gridfield>div {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
table.ss-gridfield.field {
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
@ -108,6 +111,7 @@ $gf_border_radius: 7px;
|
||||
padding: 0;
|
||||
border-right: 1px solid #85959C;
|
||||
height: 20px;
|
||||
white-space: nowrap;
|
||||
|
||||
/* Makes it appear as though the text sits over the boundary of the two <tr>'s in <thead> */
|
||||
span {
|
||||
@ -164,7 +168,7 @@ $gf_border_radius: 7px;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
color: #FFF;
|
||||
width: 95%;
|
||||
min-width: 100px;
|
||||
background: transparent;
|
||||
border: 0 none;
|
||||
@include box-shadow-none;
|
||||
|
18
security/Group.php
Normal file → Executable file
18
security/Group.php
Normal file → Executable file
@ -62,17 +62,18 @@ class Group extends DataObject {
|
||||
public function getCMSFields() {
|
||||
Requirements::javascript(SAPPHIRE_DIR . '/javascript/PermissionCheckboxSetField.js');
|
||||
|
||||
$config = new GridFieldConfig_ManyManyEditor('FirstName', false, 20);
|
||||
$config->addComponent(new GridFieldPopupForms(Controller::curr(), 'EditForm'));
|
||||
$memberList = new GridField('Members','Members', $this->Members(), $config);
|
||||
|
||||
// @todo Implement permission checking on GridField
|
||||
//$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
|
||||
//$memberList->setPopupCaption(_t('SecurityAdmin.VIEWUSER', 'View User'));
|
||||
$fields = new FieldList(
|
||||
new TabSet("Root",
|
||||
new Tab('Members', _t('SecurityAdmin.MEMBERS', 'Members'),
|
||||
new TextField("Title", $this->fieldLabel('Title')),
|
||||
$memberList = new MemberTableField(
|
||||
(Controller::has_curr()) ? Controller::curr() : new Controller(),
|
||||
"Members",
|
||||
$this,
|
||||
null,
|
||||
false
|
||||
)
|
||||
$memberList
|
||||
),
|
||||
|
||||
$permissionsTab = new Tab('Permissions', _t('SecurityAdmin.PERMISSIONS', 'Permissions'),
|
||||
@ -152,9 +153,6 @@ class Group extends DataObject {
|
||||
$rolesField->setDisabledItems($inheritedRoles->column('ID'));
|
||||
}
|
||||
|
||||
$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
|
||||
$memberList->setPopupCaption(_t('SecurityAdmin.VIEWUSER', 'View User'));
|
||||
|
||||
$fields->push($idField = new HiddenField("ID"));
|
||||
|
||||
$this->extend('updateCMSFields', $fields);
|
||||
|
3
templates/Includes/GridFieldItemEditView.ss
Normal file
3
templates/Includes/GridFieldItemEditView.ss
Normal file
@ -0,0 +1,3 @@
|
||||
<a href="$Backlink"> Go back the way you came!</a>
|
||||
|
||||
$ItemEditForm
|
4
templates/Includes/GridFieldRelationAdd.ss
Normal file
4
templates/Includes/GridFieldRelationAdd.ss
Normal file
@ -0,0 +1,4 @@
|
||||
<div><% control Fields %>
|
||||
<span>$Field</span>
|
||||
<% end_control %>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user