SilverStripe - Adding 2 TreeDropdownFields, only one works - silverstripe

I've run into a very bizarre issue while creating 2 TreeDropdownFields for a DataObject. For some reason, only 1 of the 2 TreeDropdownFields render correctly in the SilverStripe admin. The other doesn't render as a TreeDropdownField at all but just as a label:
Here is the code:
class HomeBanner extends DataObject {
public static $db = array(
'SortOrder' => 'Int',
'Title' => 'Varchar'
);
public static $has_one = array(
'Image' => 'Image',
'SecondaryImage' => 'Image',
'FirstLink' => 'SiteTree',
'SecondLink' => 'SiteTree'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeFieldFromTab('Root.Main', 'PageID');
$fields->removeFieldFromTab('Root.Main', 'SortOrder');
$fields->addFieldToTab('Root.Main', new TreeDropdownField('FirstLinkID', 'First Link', 'SiteTree'));
$fields->addFieldToTab('Root.Main', new TreeDropdownField('SecondLinkID', 'Second Link', 'SiteTree'));
return $fields;
}
public static $summary_fields = array(
'ID' => 'ID',
'Title' => 'Title',
'Thumbnail' => 'Thumbnail'
);
public function getThumbnail() {
return $this->Image()->CMSThumbnail();
}
}
Here is what I have tried so far:
running dev/build/?flush=true
running ?flush=all and ?flush=1
logging out and logging back in after the dev/build + flushes
logging into the admin in another browser (I typically use Chrome but
logged into the site's admin on FireFox and saw the same problem)
The error logs report nothing -- they're clear
There are no errors in the console for Chrome's dev tools
Adding a third TreeDropdownField will allow the first 2 to render
properly but the third one will just show a label instead of a
TreeDropdownField
This format works, but doesn't save whatever is selected--it clears your choice as soon as you leave the page. Also, it deletes all that was saved already in the admin unless I remove it. I can't make changes or else the items saved get removed.):
$fields->addFieldToTab('Root.Main', new TreeDropdownField('SecondLink', 'Second Link', 'SiteTree', 'ID'));
Does anyone have any ideas as to why this could be happening? It doesn't seem to make sense that you can't have multiple TreeDropdownFields.

Reposting as this turned out to be the answer:
The name “HomeBanner” suggests to me that there should also be a has_one pointing back to HomePage or similar? The cause of this is probably that SilverStripe is automatically trying to set one of the has_one relations to point back to the page that the banner belongs to.
Similar conflicts can also happen when using code like this:
class Page extends SiteTree {
private static $has_many = [
'Banners' => 'Banner'
];
}
class Banner extends DataObject {
private static $has_one = [
'Page' => 'Page',
'LinkedPage' => 'Page'
];
}
As SilverStripe doesn't know whether it should use PageID or LinkedPageID to auto-populate that side of the has_many relation (GridField will try to automatically assign the correct has_one ID).
In these cases, you can use dot-notation to distinguish between them - you’d change it to $has_many = ['Banners' => 'Banner.Page'];. See https://docs.silverstripe.org/en/3/developer_guides/model/relations/#has-many for more info.

Related

Silverstripe - Repeating / Reusable grouped fields

I would like to implement a layout in which a column is repeated several times and can be specified as dynamic content in the CMS. However, I cannot find an object type that would fit for this.
Do I have to specify the input fields individually for each column?
private static $db = [
'Intro_Headline' => 'Varchar',
'Intro_Subheadline' => 'Varchar',
'Intro_Text' => 'HTMLText',
'Intro_Headline2' => 'Varchar',
'Intro_Subheadline2' => 'Varchar',
'Intro_Text2' => 'HTMLText',
...
];
--
$fields = parent::getCMSFields();
//Intro Field 1
$fields->addFieldToTab('Root.Intro', TextField::create('Intro_Headline', 'Headline'), 'Content');
$fields->addFieldtoTab('Root.Intro', TextField::create('Intro_Subheadline', 'Subheadline'), 'Content');
$fields->addFieldToTab('Root.Intro', HTMLEditorField::create('Intro_Text', 'Text'), 'Content');
//Intro Field 2
$fields->addFieldToTab('Root.Intro', TextField::create('Intro_Headline2', 'Headline'), 'Content');
$fields->addFieldtoTab('Root.Intro', TextField::create('Intro_Subheadline2', 'Subheadline'), 'Content');
$fields->addFieldToTab('Root.Intro', HTMLEditorField::create('Intro_Text2', 'Text'), 'Content');
...
Or can someone tell me which field type I can't find?
Update:
Now I have a $has_many variable in addition to my $db variable in my page-model:
private static $has_many = [
'Intro_Columns' => IntroColumn::class,
];
In the getCMSFields() function I add them like this:
$fields->addFieldToTab('Root.Columns', GridField::create('Intro_Columns', 'Columns', IntroColumn::get()), 'Content');
And my data object looks like this:
class IntroColumn extends DataObject
{
private static $db = [
'img_url' => 'Text',
'headline' => 'Varchar',
'subheadline' => 'Varchar',
'text' => 'Text',
'link' => 'Text'
];
}
But the fields are not yet displayed in the CMS. How do I output the data fields from a data object?
For things to be repeatable, you will have to put them into a different object, and then link multiple of those objects to your current object/page.
GridFIeld
The default way of doing this in SilverStripe 4 is using the built in Database Relation ($has_many or $many_many instead of $db) and GridField` as the form field.
I'd recommend you go through this tutorial: https://docs.silverstripe.org/en/4/developer_guides/model/relations/
In paticular the section about $has_many will apply to your usecase. (Example where 1 Team has multiple Players or 1 Company multiple People)
$has_many/$many_many is a very generic option and can be used for any number of possible database relation (linking Categories, Images, Pages, ...)
elemental module
Another option is an officially supported module called elemental. This is very specifically built for repeatable content.
https://github.com/silverstripe/silverstripe-elemental
serialized-dataobject module
Probably not ideal for your usecase, but I maintain a module that provides an alternative to GridField, but it's more suitable for small form fields. The HTMLEditor is to large to be useful in this module.
https://github.com/Zauberfisch/silverstripe-serialized-dataobject
PS: regardless of what way you go, I highly recommend you go through the tutorial from (1.). It's a pretty important fundamental functionality of SilverStripe.
EDIT: response to your updated question:
If you are using GridField, I'd recommend the following:
class Page extends SiteTree {
private static $has_many = [
'Intro_Columns' => IntroColumn::class,
];
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Columns', new GridField(
'Intro_Columns',
'My Columns',
$this->Intro_Columns(),
new GridFieldConfig_RecordEditor()
), 'Content');
return $fields;
}
}
class IntroColumn extends DataObject {
private static $db = [
'headline' => 'Varchar',
'subheadline' => 'Varchar',
'text' => 'Text',
'link' => 'Text'
];
private static $has_one = [
'Image' => 'Image',
]
public function getCMSFields() {
$fields = new FieldList();
$fields->push(new TextField('headline', 'My Headline'));
$fields->push(new TextField('subheadline', 'My Subheadline'));
// ... and so on
$fields->push(new UploadField('Image', 'Upload an image'));
return $fields;
}
}
Note that I am using $this->Intro_Columns() as the Value for the GridField and not IntroColumn::get(). Because $this->Intro_Columns() is a automatically generated Method that returns all IntroColumn objects linked to the current page. But $this->Intro_Columns() would return all IntroColumns from all Pages
In the template, you cal also access this automatically generated method:
<!-- Page.ss -->
<h1>Page Title: $Title</h1>
<div class="intro-columns">
<% loop $Intro_Columns %>
<div class="intro-column">
<!-- here we are scoped into a single IntroColumn, so you can use all DB fields and methods of that object -->
<h2>$headline <br> $subheadline</h2>
$Image.URL <br>
...
</div>
<% end_loop %>
</div>

How to translate a Tab name and a TextField?

I am trying to translate a Tab and a TextField but it is not translating at the moment. Current setup as per below:
Locale is set in _config.php - I have flushed.
i18n::set_locale('de_DE');
mysite/lang/de.yml
de:
Page:
FULLNAME: 'Testing this'
CONTACTDETAILS: 'Root.Trying to change to this text'
Page.php
<?php
class Page extends SiteTree {
private static $db = array(
'FullName' => 'Varchar(255)'
);
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldsToTab(_t('Page.CONTACTDETAILS', 'Root.ContactDetails'), array(
TextField::create('FullName', _t('Page.FULLNAME', 'Full Name'))
));
return $fields;
}
}
But the text is not being translated it just shows in English. What am I doing wrong?
The CMS uses the currently logged in user's Locale field for translations afaik. You can change a user's locale to de_DE (whereupon your translations should work) by going to Security, selecting the user, and changing Interface Language to German (Germany).
It's also possible to set the locale inside your getCMSFields if you only want the fields in there to get translated:
public function getCMSFields()
{
$oldLocale = i18n::get_locale();
i18n::set_locale('de_DE');
$fields = parent::getCMSFields();
$fields->addFieldsToTab(_t('Page.CONTACTDETAILS', 'Root.ContactDetails'), array(
TextField::create('FullName', _t('Page.FULLNAME', 'Full Name'))
));
i18n::set_locale($oldLocale);
return $fields;
}
The Locale of new users created through the CMS will be set based on the Locale of the user that is creating them.

New GridField item on each page - SilverStripe

I am trying to use GridField to allow CMS users to add information (Mailing Lists) to an article of the same page type (Shows) but every new page that's created is already populated with information from previous articles.
I think it may have something to do with the way I've set up the relationships between the Shows page type and the Mailing List DataObject but I can't figure out what I need to do for every new show page to have it's own blank GridField as the mailing list isn't the same for every show...
I've tried many combinations of $has_one, $has_many & $many_many on the two files but nothing gets it working the way I need it to which is for every Show page to have it's own GridField (Mailing List)...
The code I have at the moment is:
Shows.php
private static $has_many = array(
'MailingLists' => 'MailingList'
);
...
$config = GridFieldConfig_RelationEditor::create();
$gridField = new GridField('MailingList',
'Shows Mailing List',
new DataList('MailingList'),
$config
);
$fields->addFieldsToTab('Root.Content.MailingList', array(
$gridField
));
MailingList.php
class MailingList extends DataObject {
private static $db = array(
'Title' => 'Varchar(25)',
'Description' => 'Varchar(55)'
);
private static $has_one = array(
'Shows' => 'Shows'
);
}
With this code
$gridField = new GridField('MailingList',
'Shows Mailing List',
new DataList('MailingList'),
$config
);
you select all records in the MailingList table with new DataList('MailingList'), but you should use related list of records with $this->MailingLists()
$gridField = new GridField('MailingList',
'Shows Mailing List',
$this->MailingLists(),
$config
);

How to place custom views area handler into views header programatically?

I had to accomplish a task by creating a custom views area handler. That all works fine, and I can place the handler into the header area via the views UI. Unfortunately, due to the deployment structure on this project, I can't just update the code relating to the view. My module creating the custom area handler also needs to alter the view to place the area handler into the header.
I've created the views area handler fine with the following code:
/**
* Implements hook_views_data().
*
* Purpose: Establish a views area handler for displaying.
*/
function custom_shipping_notification_views_data() {
// Initialize data array.
$data = array();
// Define a handler for an area used to display if qualifies for free ship.
$data['commerce_order']['free_shipping_message'] = array(
'title' => t('Free Shipping Notification'),
'help' => t('Displays the free shipping notification on cart form.'),
'area' => array(
'handler' => 'custom_shipping_notification_handler',
),
);
// Return data.
return $data;
}
That all works, so I won't get into the handler itself. I can place this via the views UI no problem. I need to place this into a specific view's header. The view is managed in features but due to the clients deployment and repo structure I cannot alter the feature reasonably. I need to alter the view from inside of my module containing the custom area.
I have attempted to use this code to no avail:
function custom_shipping_notification_views_pre_build(&$view){
if ($view->name == 'commerce_cart_form') {
$id = $view->add_item('default', 'header', 'views', 'free_shipping_message');
}
}
Anyone have any ideas? I'm obviously getting into murky territory since the add_item method has a one line description, and no coding examples. Most of the documentation around this area is similarly undeveloped.
function YOURMODULENAME_views_pre_view(&$view, &$display_id, &$args) {
if($view->name == 'YOURVIEWNAME' && $display_id == 'YOURDISPLAYID') {
$footer = "This is the text that I want in my footer!!!!";
$options = array(
'id' => 'area',
'table' => 'views',
'field' => 'area',
'empty' => FALSE,
'content' => $footer,
'format' => 'filtered_html',
'tokenize' => 0,
);
$view->set_item('YOURDISPLAYID', 'footer', 'area', $options);
}
}

simple example form in drupal 7 with everything configured correctly shows "Page not found"..Why..?

I have installed drupal 7 and have been trying to create a custom form. The below code which am trying has been taken from http://drupal.org/node/717722 and I have not made any changes except for .info file.
here is the my_module.info
name = My module
description = Module for form api tutorial
core = 7.x
Below is the my_module.module
<?php
/**
* This function defines the URL to the page created etc.
* See http://api.drupal.org/api/function/hook_menu/6
*/
function my_module_menu() {
$items = array();
$items['my_module/form'] = array(
'title' => t('My form'),
'page callback' => 'my_module_form',
'access arguments' => array('access content'),
'description' => t('My form'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* This function gets called in the browser address bar for:
* "http://yourhost/my_module/form" or
* "http://yourhost/?q=my_module/form". It will generate
* a page with this form on it.
*/
function my_module_form() {
// This form calls the form builder function via the
// drupal_get_form() function which takes the name of this form builder
// function as an argument. It returns the results to display the form.
return drupal_get_form('my_module_my_form');
}
/**
* This function is called the "form builder". It builds the form.
* Notice, it takes one argument, the $form_state
*/
function my_module_my_form($form_state) {
// This is the first form element. It's a textfield with a label, "Name"
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
);
return $form;
}
?>
I have placed these two files in a *my_module* folder and placed it in sites/all/modules
After that, I enabled the module from the modules page without any errors or warnings.
Now, when I try to access this for using the url, localhost/d7/?q=my_module/form
I get a "Page not found " error..!! Why..?? What am I missing..?
Its not only for this module but also for this examples for developers module http://drupal.org/project/examples. It shows the same error.
You should write:
$items['my_module']
Where my_module is module name.
And you need to create page-my_module_my_form.tpl.php file at
sites/all/theme/your_theme/template/page-my_module_my_form.tpl.php
and in this file add code like this:
<?php
if (isset($form['submission_info']) || isset($form['navigation'])) {
print drupal_render($form['navigation']);
print drupal_render($form['submission_info']);
}
print drupal_render($form['submitted']);
?>
<?php print drupal_render_children($form); ?>
and try to run with
localhost/d7/my_module
I hope this will be useful to you
I know this is late, but I do believe that you need to have the $form variable passed into your form, like so : function my_module_my_form($form_state, $form)... That way you actually have a form variable to house your form data.

Resources