I have custom data object with predefined entries. I don't want user to delete or add any new entries from GridField's edit form. Is there a way to remove those two buttons form ModelAdmins GridField edit form?
Using: Silverstripe 3.6
To remove actions from a GridField "globally", eg. for all records managed by the GridField, it's best to modify the GridFieldConfig instance.
In a ModelAdmin context, this is possible by overriding getEditForm:
public function getEditForm($id = null, $fields = null)
{
$form = parent::getEditForm($id, $fields);
// make sure to check if the modelClass matches the object you want to edit
// otherwise, the config will get applied to all models managed
// by this ModelAdmin instance
if ($this->modelClass === Translation::class) {
$fieldName = $this->sanitiseClassName($this->modelClass);
/** #var GridField $grid */
if ($grid = $form->Fields()->dataFieldByName($fieldName)) {
$grid->getConfig()->removeComponentsByType([
GridFieldDeleteAction::class,
GridFieldAddNewButton::class
]);
}
}
return $form;
}
However, the user might still be able to delete a record in the detail-view. But since both, GridField and detail-view respect DataObject permissions, you should make use of them… this also prevents that a user can delete the object via other means.
A simplistic solution would be (these methods should be implemented in your DataObject):
public function canDelete($member = null)
{
return Permission::check('ADMIN');
}
public function canCreate($member = null)
{
return Permission::check('ADMIN');
}
public function canView($member = null)
{
return true;
}
public function canEdit($member = null)
{
return Permission::check('CMS_ACCESS_TranslationAdmin');
}
That way, only administrators can create/delete these objects. They can be viewed by all users and edited by users that have access to your ModelAdmin section (here named "TranslationAdmin").
Ok, i got it my self. If you want to remove Add and Delete buttons from managed model's ModelAdmin you need to add this code
class Translation extends DataObject {
// ...
public function canDelete($member = null) {
return false;
}
}
class TranslationAdmin extends ModelAdmin {
public static $managed_models = ['Translation'];
static $url_segment = 'translations';
static $menu_title = 'Translations';
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$form
->Fields()
->fieldByName($this->sanitiseClassName($this->modelClass))
->getConfig()
->removeComponentsByType('GridFieldDeleteAction')
->removeComponentsByType('GridFieldAddNewButton');
return $form;
}
}
Hope this helps for someone in future.
Related
I'm trying to remove the Export to CSV button in the top of a GridField in ModelAdmin.
I can't seem to find the class that creates the button (GridFieldExportButton right?). I'm guessing there is a function that populates the GridField with buttons / "actions" which I'm not familiar with.
To remove the scaffolded GridField for relationships...
class MyDataObject extends DataObject {
...
private static $has_many= array(
'OtherDataObjects' => 'OtherDataObject'
);
...
function getCMSFields() {
$fields = parent::getCMSFields();
if($grid = $fields->dataFieldByName('OtherDataObjects'))
$grid->getConfig()
->removeComponentsByType('SilverStripe\Forms\GridField\GridFieldExportButton');
return $fields;
}
...
}
If you are making the GridField then just add this when you create the field...
$gridField->getConfig()->removeComponentsByType('SilverStripe\Forms\GridField\GridFieldExportButton');
If you are looking for a gridfield that isn't within a data object edit form and is actually...
class MyAdmin extends ModelAdmin {
...
function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
if($this->modelClass == 'MyDataObjectName') {
$form->Fields()
->fieldByName($this->sanitiseClassName($this->modelClass))
->getConfig()
->removeComponentsByType('SilverStripe\Forms\GridField\GridFieldExportButton');
}
return $form;
}
...
}
Setting model_importers to empty will do the reverse and remove the import ...
class MyAdmin extends ModelAdmin {
...
static $model_importers = array();
...
}
I wonder in a sf2 form how can I manage a form that has several save buttons (submit type).
My form is mapped on an object and I would like to only persist the personn fields when the 'savePersonn' button is clicked.
Also, I would like to only persist adress fields when the 'saveAdress' button is clicked .
Finally, I would like to save all the form infos when the 'saveAll' button is clicked.
Does anybody know how to do that ? Currently, in my handler, all informations are persisted.
The code of my formType :
<?php
namespace Foobar\OwnerBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class OwnerType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('firstName', null);
$builder->add('lastName', null);
$builder->add('dateOfBirth', null);
$builder->add('savePersonn', 'submit');
$builder->add('address', null);
$builder->add('country', null);
$builder->add('saveAdress', 'submit');
$builder->add('saveAll', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Foobar\CarBundle\Entity\Owner'
));
}
public function getName() {
return 'owner';
}
}
If what you're after is the "only save some of the entity" part rather than the "how to do different things if different buttons are clicked" part, then an option would be what I hinted at below about comparing the altered Entity with a fresh one from the DB, e.g.
$ownerRepo = $this->getRepository('Bundle:Owner');
$changedOwner = $ownerRepo->find($id);
//Handle request etc, $changedOwner gets data from submitted form
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
if ($form->get('saveAll')->isClicked() )
{
$em->flush(); //$changedOwner was already managed, just need to save all changes
}
elseif ($form->get('saveAdress')->isClicked() )
{
//Save just address
$em->detach($changedOwner); //Don't want everything in this to be persisted
$freshOwner = $ownerRepo->find($id); //untouched
$freshOwner->setAddress($changedOwner->getAddress() ); //and other fields
$em->flush(); //Update $freshOwner
}
elseif ($form->get('savePersonn')->isClicked() )
{
//Save just person
//As above, but with person rather than address
}
return $this->redirect();
}
Old answer on wrong track
As described in the Symfony Form Docs, in your Controller you can test which button has been clicked like this:
if ($form->isValid()) {
if ($form->get('saveAll')->isClicked() )
{
//Save entire entity
}
elseif ($form->get('saveAdress')->isClicked() )
{
//Save just address
}
elseif ($form->get('savePersonn')->isClicked() )
{
//Save just person
}
return $this->redirect();
}
You'll have to decide for yourself how to save only part of the Entity; it may involve retrieving another copy from the DB, comparing it with the one populated from the form, and selectively updating only the relevant fields before persisting.
I would like to use GridField to view and create new child pages. Parent is DocumentHolder, child is Document. Both extend SiteTree. When I click to "Add Document" (button generated by grid), fill in the fields and confirm the form, the parent page is ignored and the page is created in root. It works well when I use DataObject. The code looks like this:
class DocumentHolder extends SiteTree
{
private static $allowed_children = array(
'Document'
);
private static $default_child = "Document";
public function getCMSFields()
{
$fields = parent::getCMSFields();
$gridField = new GridField('Documents', 'Documents', SiteTree::get('Document')->filter('ParentID', $this->ID), GridFieldConfig_RecordEditor::create());
$fields->addFieldToTab("Root.Uploads", $gridField);
return $fields;
}
}
class Document extends SiteTree
{
private static $db = array(
);
private static $has_one = array(
);
}
Thanks for help.
Since SiteTree already has a relationship to its Children pages set up, you may as well use it! Since allowed_children will only ever be documents, try this instead:
$gridField = new GridField('Documents', 'Documents', $this->Children(), GridFieldConfig_RecordEditor::create());
I ran into this problem earlier working on my holderpage module. You need to set the ParentID by default. Here's two strategies;
You can use populateDefaults on the child class. E.g.
class Document extends SiteTree
{
private static $default_parent = 'DocumentHolder';
private static $can_be_root = false;
public function populateDefaults(){
parent::populateDefaults();
$this->ParentID = DataObject::get_one(self::$default_parent)->ID;
}
...
Or you can manipulate the record in the gridfield with a custom GridFieldDetailForm implementation or via the updateItemEditForm callback.
<?php
class MyGridFieldDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest
{
public function ItemEditForm()
{
$form = parent::ItemEditForm();
if (! $this->record->exists() && $this->record->is_a('SiteTree')) {
$parent_page = $this->getController()->currentPage();
if ($parent_page && $parent_page->exists()) {
$this->record->ParentID = $parent_page->ID;
// update URLSegment #TODO perhaps more efficiently?
$field = $this->record->getCMSFields()->dataFieldByName('URLSegment');
$form->Fields()->replaceField('URLSegment', $field);
}
}
return $form;
}
}
This is more complicated although it allowed me to create an effortless module / addon ( https://github.com/briceburg/silverstripe-holderpage )
Assume we have singleton class
class Registry {
private static $_instance;
private function __construct() {}
private function __wakeup() {}
private function __clone() {}
private $_map = array();
public static function getInstance () {
if (self::$_instance === null)
self::$_instance = new self();
return self::$_instance;
}
public function set ($key, $val) {
self::getInstance()->_map[$key] = $val;
return self::getInstance();
}
public function get($key)
{
if (array_key_exists($key, self::getInstance()->_map))
return self::getInstance()->_map[$key];
return null;
}
}
And we have simple Symfony2 Controller with 2 actions
class IndexController {
public function indexAction () {
Registry::getInstance()->set('key',true);
return new Response(200);
}
public function secondAction () {
$val = Registry::getInstance()->get('key');
return new Response(200);
}
}
I call index action, then second action. But I can't find key, that was set in first action. I think, new instance of singleton creates in my second action. Why object is not saved in memory? What do I do wrong?
If you call indexAction and secondAction in different requests it won't work the way you want it because your Registry instance is not shared between requests.
Singleton itself does not store anything "in memory" (BTW Singleton is now considered as an anti-pattern).
What, I think, you want to achieve can be done by using session storage. Check doc for more info how to implement this.
If I have a $has_many relationship that I want to manage with a GridField in the cms, how would I go about putting a limit on the number of how many relations one object can have? Is this possible?
Can I do this in the model or would it have to be a check I add into the GridField I'm using to add and remove relations?
I'm looking at implementing GridField_SaveHandler to make a custom GridFieldComponent but not sure how I can use this to abort the save if i detect something is wrong.
the following 2 solutions are not the cleanest way to solve this, but the most pragmatic and easiest to implement.
basically, what I suggest to do, is just count the objects and remove the ability to add new records once the count is above a certain number.
if you want to limit the number of records on a single relation/grid (lets say max 5 players per team):
class Player extends Dataobject {
private static $db = array('Title' => 'Varchar');
private static $has_one = array('TeamPage' => 'TeamPage');
}
class TeamPage extends Page {
private static $has_one = array('Players' => 'Player');
public function getCMSFields() {
$fields = parent::getCMSFields();
$config = GridFieldConfig_RecordEditor::create();
if ($this->Players()->count > 5) {
// remove the buttons if we don't want to allow more records to be added/created
$config->removeComponentsByType('GridFieldAddNewButton');
$config->removeComponentsByType('GridFieldAddExistingAutocompleter');
}
$grid = GridField::create('Players', 'Players on this Team', $this->Players(), $config);
$fields->addFieldToTab('Root.Main', $grid);
return $fields;
}
}
if you want to limit the total number of records globally (if we limit this way to 5, this means if 1 Team already has 3 Players, then the 2nd team can only have 2):
class Player extends Dataobject {
private static $db = array('Title' => 'Varchar');
private static $has_one = array('TeamPage' => 'TeamPage');
public function canCreate($member = null) {
if (Player::get()->count() > 5) {
return false;
}
return parent::canCreate($member);
}
}
class TeamPage extends Page {
private static $has_one = array('Players' => 'Player');
public function getCMSFields() {
$fields = parent::getCMSFields();
$config = GridFieldConfig_RecordEditor::create();
$grid = GridField::create('Players', 'Players on this Team', $this->Players(), $config);
$fields->addFieldToTab('Root.Main', $grid);
return $fields;
}
}
I have wrote a quick jQuery plugin to limit the number of items a GridField can have: -
Download the plugin here: - gridfieldlimit.js
https://letscrate.com/f/monkeyben/silverstripe/gridfieldlimit.js
Set up the plugin in the getCMSFields function: -
// Pass GridField configs, each one containing field name and item limit
$vars = array(
"GridFieldLimits" => "[['GRIDFIELD_NAME_1', 3], ['GRIDFIELD_NAME_2', 6]]",
);
// Load the jquery gridfield plugin
Requirements::javascriptTemplate("themes/YOUR_THEME_NAME/javascript/gridfieldlimit.js", $vars);
works for me: make the canCreate method of the DataObject that's managed by your GridField check for existing objects.
of course, this doesn't allow you to implement a customg GridFieldComponent, as you need to modify the DataObject code.