So, disclaimer first: I'm a bit of a noob when it comes to SilverStripe, but this one is vexxing me.
I'm using GridField to add and edit the entries in a DataObject. This is all well and good, and works perfectly. The only thing I can't figure out is how to change the order of the EDITABLE fields - this isn't the initial table display of the entries (which is set by $config), it's the actual input fields once you click "add new" or go to edit a record.
At the moment the Image uploadForm and the Signature <select> box are below the Body HTMLText field, which is messy and doesn't work right. I want them up the top, right below the Summary element.
I've tried playing around with changeFieldOrder(), but that doesn't work on a GridField object type and $fields doesn't know anything about the input elements (I dump()'ed it and had a look).
MediaReleaseItem.php:
class MediaReleaseItem extends DataObject {
static $db = array (
'Title' => 'Varchar',
'DateUpdated' => 'Date',
'Summary' => 'Varchar',
'Image' => 'Varchar',
'Body' => 'HTMLText',
);
private static $has_one = array(
"Image" => "Image",
"MediaReleaseItem" => "MediaReleases",
"Signature" => "MediaReleaseSignature",
);
}
And MediaReleases.php:
class MediaReleases extends Page {
private static $has_many = array(
"MediaReleaseItems" => "MediaReleaseItem",
"Signature" => "MediaReleaseSignature",
);
function getCMSFields() {
$fields = parent::getCMSFields();
$config = GridFieldConfig_RecordEditor::create();
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Title'=> 'Title',
'DateUpdated' => 'Date',
'Summary' => 'Summary',
));
$mediaReleasesField = new GridField(
'MediaReleaseItem', // Field name
'Media Releases', // Field title
$this->MediaReleaseItems(),
$config
);
$fields->addFieldToTab('Root.MediaReleaseItems', $mediaReleasesField);
return $fields;
}
}
(Signature is just another DataObject with a different GridField on a different tab, I didn't include the code for it because it's almost identical.)
so, you mean when you edit a MediaReleaseItem the fields are not the way you want then to be?
simple: just also define a method getCMSFields() on the class MediaReleaseItem.
<?php
class MediaReleaseItem extends DataObject {
private static $db = array (
'Title' => 'Varchar',
'DateUpdated' => 'Date',
'Summary' => 'Varchar',
'Image' => 'Varchar',
'Body' => 'HTMLText',
);
private static $has_one = array(
"Image" => "Image",
"MediaReleaseItem" => "MediaReleases",
"Signature" => "MediaReleaseSignature",
);
public function getCMSFields() {
$arrayOfSignatures = MediaReleaseSignature::get()->map()->toArray();
$fields = FieldList::create(array(
TextField::create('Title', 'Title for this Item'),
DateField::create('DateUpdated', 'Updated')->setConfig('showcalendar', true),
TextField::create('Image', 'Image'),
// not sure if it works to have both a DB field and a has_one with the same name
UploadField::create('ImageID', 'Image'),
DropdownField::create('Signature', 'Signature', $arrayOfSignatures),
// you can add more fields here
));
// but you can also add fields here
$fields->insertBefore(TextField::create('Summay', 'Summary'), 'DateUpdated');
$fields->push(HTMLEditorField::create('Body', 'Body Content'));
return $fields;
}
}
Related
I am trying to use a Has_many relation as the summary fields for a DataObject and can't seem to get it working.
Basically:
I have a Form
each form has many submissions/entries
each form has many fields
Each field has many answers.
I'm trying to create a gridfield in the back end admin area of each form which displays the entries for each form.
In the summary fields for the entry, i'd like to display the Date created, and the first 3 fields for that form.
So, for example if we had a form with a name, email, and phone field the summary fields would be as follows:
Date Created
Name
Email
Phone
with the relevent entry data/responses as summary information for the entry.
Here is what I have so far. This is the form:
<?php
class ContactBlock extends Block {
private static $db = array(
// Fields for the form/block go here
);
private static $has_many = array(
'ContactBlockFields' => 'ContactBlockField',
'ContactBlockEntries' => 'ContactBlockEntry'
);
public function getCMSFields() {
// Irrelevant code goes here!
// CONTACT BLOCK ENTRIES
$entriesInfo = new GridFieldDataColumns();
$entriesInfo->setDisplayFields(singleton('ContactBlockEntry')->summaryFields());
$entriesConfig = GridFieldConfig::create();
$entriesConfig->addComponents(
new GridFieldToolbarHeader(),
new GridFieldAddNewButton('toolbar-header-right'),
$entriesInfo,
new GridFieldSortableHeader(),
new GridFieldPaginator(50),
new GridFieldEditButton(),
new GridFieldDeleteAction(),
new GridFieldDetailForm()
);
$entriesGrid = GridField::create('ContactBlockEntries', 'Form Entries', $this->ContactBlockEntries(), $entriesConfig);
$fields->addFieldToTab('Root.FormEntries', $entriesGrid);
return $fields;
}
}
This is the Entry DataObject:
<?php
class ContactBlockEntry extends DataObject {
private static $has_one = array(
'ContactBlock' => 'ContactBlock'
);
private static $has_many = array(
'ContactBlockFieldAnswers' => 'ContactBlockFieldAnswer',
);
private static $many_many = array(
'FormFields' => 'ContactBlockField'
);
static $summary_fields = array(
'Date'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
//=== REMOVE FIELDS ====
$fields->removeFieldFromTab('Root','FormFields');
$fields->removeFieldFromTab('Root','ContactBlockFieldAnswers');
$fields->removeFieldFromTab('Root.Main','ContactBlockID');
//=== REMOVE FIELDS ====
return $fields;
}
public function onBeforeDelete() {
parent::onBeforeDelete();
// Delete answers that are associated with this block.
$data = ContactBlockFieldAnswer::get()
->filter('ContactBlockEntry', $this->ID);
foreach( $data as $d) {
$d->delete();
}
}
public function getDate() {
$date = date('d/m/Y',strtotime($this->Created));
return $date;
}
}
This is the field code:
<?php
class ContactBlockField extends DataObject {
private static $db = array(
'SortOrder' => 'Int',
'FieldName' => 'Varchar',
'FieldType' => 'Varchar',
'DropdownValues' => 'Varchar(255)',
'Label' => 'Varchar',
'Placeholder' => 'Varchar',
'Required' => 'Boolean'
);
private static $has_one = array(
'ContactBlock' => 'ContactBlock',
);
private static $has_many = array(
'ContactBlockFieldAnswer' => 'ContactBlockFieldAnswer',
);
private static $belongs_many_many = array(
'Entries' => 'ContactBlockEntry'
);
static $searchable_fields = array(
'FieldType',
'FieldLabel',
'Required'
);
static $summary_fields = array(
'FieldType' => 'Field Type',
'Label' => 'Field Label',
'Required' => 'Required Field?'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
// Unrelated stuff here
return $fields;
}
}
I can't seem to figure out how to get the column labels, and their relevant data showing on the gridfield. Any help or advice would be much appreciated.
UPDATE 24/3/17:
OK I've got a little further with this. On the ContactBlockEntry DataObject, after implementing the changes suggested by UncleCheese, I have discovered the following:
public function getFirstField() {
return $this->FormFields()->first();
}
public function getSecondField() {
return $this->FormFields()->offsetGet(1);
}
public function getThirdField() {
return $this->FormFields()->offsetGet(2);
}
public function summaryFields() {
return [
'Date' => 'Submitted',
'Answers.First.Value' => $this->getFirstField()->Label,
'Answers.Second.Value' => $this->getSecondField()->Label,
'Answers.Third.Value' => $this->getThirdField()->Label,
];
}
$this->getFirstField()->Label is returning [Notice] Trying to get property of non-object however, when I echo/print this function in getCMSFields() I can see the label value.
Answers.First.Value works. It returns the value/answer submitted in the first field. The problem is, I can't seem to get the second and third values, as I can't figure out the method to retrieve them. I tried offsetGet() and it said the method isn't available.
In this case, you can define a summaryFields() method in lieu of the static array. This will allow you to return a computed value for the headings.
What complicates things is getting the values. The first three fields are not properties of the Entry object, so you'll need to provide getters for the Entry dataobject to compute them on the fly as "virtual" properties. It's a bit clunky, but something like this should work.
public function getFirstField()
{
return $this->FormFields()->first();
}
public function getSecondField()
{
return $this->FormFields()->offsetGet(1);
}
public function getThirdField()
{
return $this->FormFields()->offsetGet(2);
}
public function summaryFields()
{
return [
'Created' => 'Created',
'FirstField.Answer' => $this->getFirstField()->FieldName,
'SecondField.Answer' => $this->getSecondField()->FieldName,
'ThirdField.Answer' => $this->getThirdField()->FieldName,
];
}
I'm not too sure about your data model, though. You have the answers has a has_many on your field object. You would in that case have to create another getter on your Field object for AnswerLabel that somehow concatenated all the answers into a string, or maybe just got the first one. Whatever business logic you choose. Just use FirstField.AnswerLabel etc. in your array, or whatever you choose to call that method. The point being, you need to resolve a plural relationship into a single readable value. How you do that is up to you.
Unable to locate in the SilverStripe Documentation how to have a DataObject Model inject a collection of default records on /dev/build
Anybody able to point me in the right direction
This is what I currently have, and obviously I would like to inject pre-configured options into this aptly named Configuration model for my Module.
class Configuration extends DataObject
{
private static $db = array(
'Option' => 'Varchar',
'Value' => 'Varchar'
);
private static $summary_fields = array(
'Option' => 'Option',
'Value' => 'Value',
);
}
Thanks in advance for any direction/pointers.
UPDATE
I was turned onto SiteConfig by #Barry below
However in following his practice, requireDefaultRecords() is not injecting defaults
Note: I have since revisited /dev/build?flush
class RMSConfiguration extends DataExtension
{
private static $db = array(
'username' => 'Varchar',
'password' => 'Varchar',
'agent_id' => 'Varchar(15)',
'client_id' => 'Varchar(15)',
'testMode' => 'Int(1)',
'timezone' => 'Varchar',
'apiUrl' => 'Varchar(255)'
);
public function updateCMSFields(FieldList $fields)
{
$fields->addFieldsToTab(
"Root.RMSConfig",
array(
TextField::create('username', 'RMS Username'),
TextField::create('password', 'RMS Password'),
TextField::create('agent_id', 'RMS Agent ID'),
TextField::create('client_id', 'RMS Client ID'),
TextField::create('apiUrl', 'API Url'),
CheckboxField::create("testMode", 'Toggle Test Mode'),
DropdownField::create("timezone", 'Timezone', static::$timezones)
)
);
}
public function requireDefaultRecords()
{
parent::requireDefaultRecords();
$arrOptions = array(
'timezone' => 'Australia/Sydney',
'apiUrl' => 'https://api.example.com.au/',
'testMode' => 0
);
foreach ($arrOptions as $strOption => $strValue) {
if (!$configuration = self::get()->filter('Option', $strOption)->first()) {
$configuration = self::create(array( 'Option' => $strOption ));
}
$configuration->Value = $strValue;
$configuration->write();
}
}
/**
* List of timezones supported by PHP >=5.3.x
*
* #var array
*/
public static $timezones = array(
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
...
...
"Zulu"
);
}
Using the function requireDefaultRecords in the DataObject - this is called during every dev/build.
Note: First check if the option exists to prevent duplicates as this will be called every time you dev build.
class Configuration extends DataObject {
private static $db = array(
'Option' => 'Varchar',
'Value' => 'Varchar'
);
private static $summary_fields = array(
'Option' => 'Option',
'Value' => 'Value',
);
function requireDefaultRecords() {
parent::requireDefaultRecords();
$arrOptions = array(
'Option1' => 'Value1',
'Option2' => 'Value2',
'Option3' => 'Value3',
);
foreach ($arrOptions as $strOption => $strValue) {
if (!$configuration = Configuration::get()->filter('Option',$strOption)->first())
$configuration = Configuration::create(array('Option' => $strOption));
$configuration->Value = $strValue;
$configuration->write();
}
}
}
One final comment is that there is a module for SiteConfig which is used by SilverStripe, most modules and where I would recommend you put configuration values like this instead.
If you do choose SiteConfig then please see the function populateDefaults and documentation for it's use, this is an example...
/**
* Sets the Date field to the current date.
*/
public function populateDefaults() {
$this->Date = date('Y-m-d');
parent::populateDefaults();
}
(if the above is used in an extensions it might need $this->owner->Date instead of $this->Date)
The above function isn't needed if all the values are static, instead it will read them just from this array (again within DataObject)
public static $defaults = array(
'Option1' => 'Value1',
'Option2' => 'Value2'
);
This works on any DataObject as well, but as SiteConfig manages one record and this populates that record once upon creation this is much more convenient for to use instead of requireDefaultRecords.
I have two user groups Administrator and Inhaltsautoren
My LandingPage has a Tab Teaser with a gridField. The normal user can not see the entries and i dont know why?
I cant find something for setting the permissions for Inhaltsautoren. Has someone an idea why there are no entries in the gridField?
Teaser.php
<?php
class Teaser extends DataObject {
private static $db = array (
'Title' => 'Varchar',
'Description' => 'HTMLText'
);
private static $has_one = array (
'Photo' => 'Image',
'Link' => 'Link'
);
private static $many_many = array(
'Tags' => 'Tag'
);
private static $summary_fields = array (
'GridThumbnail' => '',
'Title' => 'Titel',
'Description' => 'Beschreibung'
);
public function getGridThumbnail() {
if($this->Photo()->exists()) {
return $this->Photo()->SetWidth(100);
}
return "(no image)";
}
public function getCMSFields() {
$fields = FieldList::create(
TextField::create('Title'),
$tags = TagField::create('Tags','Tags',Tag::get(),$this->Tags()),
HTMLEditorField::create('Description', 'Beschreibung'),
LinkField::create('LinkID', 'Weiterleitung'),
$uploader = UploadField::create('Photo')
);
$tags->setShouldLazyLoad(true); // tags should be lazy loaded
$tags->setCanCreate(true); // new tag DataObjects can be created
$uploader->setFolderName('teaser');
$uploader->getValidator()->setAllowedExtensions(array('png','jpeg','jpg'));
return $fields;
}
}
and my LadingPage.php
$fields->addFieldToTab('Root.Teaser', $gridField = GridField::create(
'Teasers',
'Landing Page Teaser',
$this->Teasers(),
GridFieldConfig_RecordEditor::create()
));
$gridField->getConfig()->getComponentByType("GridFieldDataColumns")->setFieldCasting(array("Description"=>"HTMLText->BigSummary"));
Use canView() on your dataobject and check inside this function if your user is allowed to see this object or not.
public function canView($member = null) {
return Permission::check('ADMIN', 'any');
}
I am working on an edit screen for my grid row. This is what I have so far for this form:
<?php
class Intellibi_Integration_Block_Adminhtml_Manageasendiapickinglists_Edit_Tab_Form extends Mage_Adminhtml_Block_Widget_Form
{
protected function _prepareForm()
{
$form = new Varien_Data_Form();
$this->setForm($form);
$fieldset = $form->addFieldset('integration_form', array(
'legend' => Mage::helper('integration')->__('Asendia Pick Information')
));
$fieldset->addField('order_number', 'label', array(
'label' => Mage::helper('integration')->__('Order Number'),
'name' => 'order_number'
));
// snipped
$fieldset->addField('pick_status', 'select', array(
'required' => false,
'class' => 'required-entry',
'label' => Mage::helper('integration')->__('Pick Status'),
'name' => 'pick_status',
'values' => Mage::getSingleton('ibi/asendiapickstatus')->getOptionArray(),
'readonly' => 'readonly'
));
// snipped
return parent::_prepareForm();
}
}
This produces the following output in the admin backend:
What I would like to do is change the pick_status column from a select to a label. When I do this, instead of showing the status value "New" it shows the array index like this:
My option array for asendiapickstatus is defined like this in my model:
class Intellibi_Integration_Model_Asendiapickstatus extends Varien_Object
{
const PICK_STATUS_NEW = 1;
const PICK_STATUS_SENT = 2;
const PICK_STATUS_SHIPPED = 3;
static public function getOptionArray()
{
return array(
self::PICK_STATUS_NEW => Mage::helper('integration')->__('New'),
self::PICK_STATUS_SENT => Mage::helper('integration')->__('Sent'),
self::PICK_STATUS_SHIPPED => Mage::helper('integration')->__('Shipped')
);
}
}
So my question is; on the edit form fieldset builder, how do I show the dropdown field "pick_status" value, rather than the current index it's at? So the output will say "New" instead of "1" as shown above. Will I need a custom renderer?
I've solved it like this (with a custom form rendered element):
Added custom fieldset type
$fieldset->addType('pickstatus', 'Intellibi_Integration_Block_Adminhtml_Manageasendiapickinglists_Edit_Tab_Form_Renderer_Fieldset_Pickstatus');
Used the fieldset like this
$fieldset->addField('pick_status', 'pickstatus', array(
'label' => Mage::helper('integration')->__('Pick Status'),
'name' => 'pick_status',
));
Coded the rendered like this
class Intellibi_Integration_Block_Adminhtml_Manageasendiapickinglists_Edit_Tab_Form_Renderer_Fieldset_Pickstatus extends Varien_Data_Form_Element_Abstract
{
protected $_element;
public function getElementHtml()
{
// Load Pick Status
$pick_status = (int)$this->getValue();
$pick_status_list = Mage::getSingleton('ibi/asendiapickstatus')->getOptionArray();
// Finish
return array_key_exists($pick_status, $pick_status_list) ? $pick_status_list[$pick_status] : 'Unknown';
}
}
And it renders like this
I'm trying to validate a field inside a Collection.
The Collection refers to Company Areas and is tied to a Company Fieldset
The validation needs to check that the Area Name doesn't exists for that Company in the Database yet.
I'm trying to do this using a Callback validator within my collection element 'area_name', my problem is that the collection is aware only of its own context, that means all fields associated to the Area but not aware of the Company context, so i can't filter my validator by its Company parent.
Is there a way to access the parent context of a collection? or should i need to initialize my form passing the Company object to the Collection prior validating?
EDIT: I forgot to mention that i'm using Doctrine2 so i'm not sure if it is possible to use the Db_NoRecordExists Validator bundled with ZF2
This is an old question and you might have fixed this already, but I had a similar problem recently.
You can create a function in your area model/service: validateAreaCompanyRelation(area, company)and in your fieldset use the callback to use it:
AreaService class:
add a method to return true or false based on query limited by 1 row.
in my case it was somthing like this:
public function validateAreaCompanyRelation($company, $area)
{
$result = false;
$count = $this->getRepository()
->createQueryBuilder('q')
->select('q')
->innerJoin('q.company', 'c')
->innerJoin('q.area','b')
->where('b.id = :area and c.company = :company')
->setParameter('area',$area)
->setParameter('company',$area)
->setMaxResults( 1 )
->getQuery()
->getArrayResult();
if(count($count) <>1){
$result=true;
}
return $result;
}
Area Field set:
inject AreaService to the field set (pass it to construct in factory)
class AreaFieldset extends Fieldset implements InputFilterProviderInterface
{
private $areaService;
public function __construct(areaServiceEntityService $areaService)
{
$this->areaService = $areaService;
}
public function init()
{
$this->add(
array(
'name' => 'area',
'filters' => array(),
'validators' => array (
array(
'name' => 'Zend\Validator\Callback',
'options' => array(
'messages' => array(
\Zend\Validator\Callback::INVALID_VALUE => 'Your custom error message',
),
'callback' => array($this,'vlidateUniqueRelation'),
),
),
)
)
);
array(
'name' => 'company',
'filters' => array(),
'validators' => array (
array(
'name' => 'Zend\Validator\Callback',
'options' => array(
'messages' => array(
\Zend\Validator\Callback::INVALID_VALUE => 'Your custom error message',,
),
'callback' => array($this,'vlidateUniqueRelation'),
),
),
)
)
);
}
public function vlidateUniqueRelation($value, $context)
{
// $value = value
// $context['xxxx'] = xxxxx value
// Logic to validate goes here
$context["company"]
$context["area"]
return $this->AreaService->validateAreaCompanyRelation($context["company"], $context["Area"]);
}