How to sort images as part of a many_many in silverstripe? - silverstripe

I have created a $many_many array for $slideImages on my home page. After much reading and trying I am still unable to tell the CMS the order I want the images to appear in the template. By default they are sorted by upload date I believe.
I can create the gridfield but I can't seem to get a textfield to enter in a sorting number. Right now I just finished a rabbit trail that led me to gridFieldComponent but I get an error and the docs are not helping me.
use SilverStripe\Assets\Image;
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\GridField\GridFieldComponent;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
use SilverStripe\Forms\GridField\GridField;
class HomePage extends Page
{
private static $db = [];
private static $has_one = [];
private static $many_many = [
'SliderImage'=>Image::Class
];
private static $owns = [
'SliderImage'
];
private static $many_many_extraFields= [
'SliderImage'=>array(
'Sort'=>'Int'
)
];
public function getCMSFields(){
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Attachments', UploadField::create('SliderImage', 'Sllider Images')->setFolderName('HomePageSlides'));
$gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
new GridFieldComponent(TextField('Sort'))
);
$gridField = new GridField("SliderImage", "Slider Image", $this->SliderImage()->sort('Sort'), $gridFieldConfig);
$fields->addFieldToTab("Root.Attachments", $gridField);
return $fields;
}
}
The Error I get is:
"Uncaught Error: Cannot instantiate interface
SilverStripe\Forms\GridField\GridFieldComponent"

As per wmk's comment, your are missing a new or ::create on the TextField initialization for your Sort field.
Should be:
new GridFieldComponent(new TextField('Sort'))
Or better yet:
GridFieldComponent::create(TextField::create('Sort'))

Related

Accessing & Outputting $ElementalArea in Silverstripe 4

In a Silverstripe 4 project we have the following method under PageController.php for outputting the content of a page as JSON:
class PageController extends ContentController
{
private static $allowed_actions = array(
'json'
);
public function json(HTTPRequest $request)
{
$data = array();
$data['ID'] = $this->ID;
$data['Title'] = $this->Title;
$data['Breadcrumbs'] = $this->obj('Breadcrumbs')->forTemplate();
$data['Content'] = $this->obj('Content')->forTemplate();
$this->response->addHeader('Content-Type', 'application/json');
return json_encode($data);
}
}
Now I would like to do the same thing with a page running the Elemental module. Elementals allows page content to generated by a number of dynamic/ configurable blocks.
To access elemental I use the following template code: $ElementalArea - which returns generated HTML.
I need to replace the following line with one that returns the HTML generated by $ElementalArea:
$data['Content'] = $this->obj('Content')->forTemplate();
I'm not sure of the correct way to do this, any suggestions are appreciated.
Exactly the same way - $this->ElementalArea()->forTemplate().

How to translate $url_handlers?

I have a situation where I need to translate the following $url_handlers for different countries.
So on an english site the URL looks like this: http://website.com/gyms/boston/group-training
I need to be able to translate the "group-training" part of the URL. I have translated the rest of the site using the _t() method throughout.
My current setup:
class GymLocationPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'currentSpecials',
'sevenDayFreeTrial',
'groupTraining'
);
private static $url_handlers = array(
'current-specials' => 'currentSpecials',
'trial' => 'sevenDayFreeTrial',
'group-training' => 'groupTraining'
);
}
How would one achieve this?
You could update the config inside the controller's init() function, doing something like this:
public function init() {
parent::init();
// Define your translated actions.
$translatedCurrentSpecials = _t('Actions.CURRENT_SPECIALS', 'aktuella-kampanjer');
$translatedSevenDayFreeTrial = _t('Actions.SEVEN_DAY_TRIAL', 'sjudagars-prova-pa-period');
// Define your url handlers.
$urlHandlers = $this->config()->url_handlers;
$translatedUrlHandlers = [
$translatedCurrentSpecials => 'currentSpecials',
$translatedSevenDayFreeTrial => 'sevenDayFreeTrial'
];
// Update the config.
Config::inst()->update(
$this->class,
'url_handlers',
$translatedUrlHandlers + $urlHandlers // Important to prepend and not append.
);
}

Sorting list of VirtualPages on a field from its Page

I have an AreaPage with $many_many VirtualPages:
class AreaPage extends Page {
/**
* #var array
*/
private static $many_many = [
'RelatedVirtualPages' => 'VirtualPage'
];
// ...
}
The RelatedVirtualPages are copying content from ContentPages:
class ContentPage extends Page {
/**
* #var array
*/
private static $db = [
'Highlighted' => 'Boolean'
];
// ...
}
What's the best way to sort RelatedVirtualPages on the Highlighted db field of the ContentPage that it's copying?
Virtual Pages could be pointed at pages of different types and there is no enforcement that all of those pages are ContentPages, or at least pages that have a Hightlighted db field. You can ensure this manually when you create your SiteTree, but users could come along and screw it up so keep this in mind.
Here is some psuedo-code that might help you get started. It assumes that all virtual pages are ContentPages. If you will have multiple types of VirtualPages referenced by an AreaPage then this is probably not sufficient.
$virtualPages = $myAreaPage->RelatedVirtualPages();
$contentSourcePages = ContentPage::get()->byIDs($virtualPage->column('CopyContentFromID'));
$sortedSourcePages = $contentSourcePages->sort('Highlighted','ASC');
You possibly could also use an innerJoin, but then you have to deal with _Live tables and possibly multiple page tables (again if not just using ContentPage as VirtualPage) which could lead to some complicated scenarios.
Update
So, to summarize in my own words, you need a list of the VirtualContentPages linked to a specific AreaPage sorted on the Highlighted field from the ContentPage that each VirtualContentPage links to. If this summary is accurate, would this work:
$sortedVirtualPages = $myAreaPage->RelatedVirtualPages()
->innerJoin('ContentPage', '"ContentPage"."ID" = "VirtualContentPage"."CopyContentFromID"')
->sort('Highlighted DESC');
I could not find a very clean method, but did find two ways to achieve this. The function goes in the class AreaPage
First
public function getRelatedVirtualPages()
{
$items = $this->getManyManyComponents('RelatedVirtualPages');
$highlighted = $items->filterByCallback(function($record, $list) {
if($record->CopyContentFrom() instanceOf ContentPage) {
//return ! $record->CopyContentFrom()->Highlighted; // ASC
return $record->CopyContentFrom()->Highlighted; // DESC
}
});
$highlighted->merge($items);
$highlighted->removeDuplicates();
return $highlighted;
}
Second (the method you described in the comments)
public function getRelatedVirtualPages()
{
$items = $this->getManyManyComponents('RelatedVirtualPages');
$arrayList = new ArrayList();
foreach($items as $virtualPage)
{
if($virtualPage->CopyContentFrom() instanceOf ContentPage) {
$virtualPage->Highlighted = $virtualPage->CopyContentFrom()->Highlighted;
$arrayList->push($virtualPage);
}
}
$arrayList = $arrayList->sort('Highlighted DESC');
return $arrayList;
}
I'm not very proud of any of these solutions, but I believe they do fit your criteria.
Here's what I ended up doing, which I think works:
/**
* #return ArrayList
*/
public function VirtualPages()
{
$result = [];
$virtualPages = $this->RelatedVirtualPages();
$contentPages = ContentPage::get()
->byIDs($virtualPages->column('CopyContentFromID'))
->map('ID', 'Highlighted')
->toArray();
foreach($virtualPages as $virtualPage) {
$highlighted = $contentPages[$virtualPage->CopyContentFromID];
$virtualPage->Highlighted = $highlighted;
$result[] = $virtualPage;
}
return ArrayList::create(
$result
);
}
And then it's sortable like so:
$areaPage->VirtualPages()->sort('Highlighted DESC');
Thank you for all the answers and pointers. I'll wait a bit before marking any answer.
Couldn't you just do
//just get one areapage
$AreaPageItem = AreaPage::get()->First();
//now get the RelatedVirtualPages sorted
$related_pages = $AreaPageItem->RelatedVirtualPages()->sort("Highlighted","ASC");

SilverStripe gridfield multi class editor has invalid sort() column

I am having trouble setting the SortOrder of a nested has_many relationship where the child items are drag and drop reorder-able though the Orderable Rows module of Gridfield Extensions. I am also using the Multi Class Editor
I have a Page that has many ComplexStrips. These ComplexStrip objects have many 'LinkedObjects'.
The issue:
When I try to add a ComplexStrip via the GridField I get the following error: Uncaught InvalidArgumentException: Invalid sort() column
My Setup:
Page Class:
This class is working fine when I browse to edit it in the CMS.
<?php
class Page extends SiteTree {
private static $has_many = array(
'ComplexStrips' => 'ComplexStrip'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$dataColumns = new GridFieldDataColumns();
$dataColumns->setDisplayFields(
array(
'Title' => 'Title',
'ClassName' => 'Class Name'
)
);
$multiClassConfig = new GridFieldAddNewMultiClass();
$multiClassConfig->setClasses(
array(
'ComplexStrip'
)
);
$stripsGridField = GridField::create('ComplexStrips', 'Complex strips', $this->Strips(),
GridFieldConfig::create()->addComponents(
new GridFieldToolbarHeader(),
new GridFieldDetailForm(),
new GridFieldEditButton(),
new GridFieldDeleteAction('unlinkrelation'),
new GridFieldDeleteAction(),
new GridFieldOrderableRows('SortOrder'), // This line causes the issue
new GridFieldTitleHeader(),
$multiClassConfig,
$dataColumns
)
);
$fields->addFieldsToTab(
'Root.Strips',
array(
$stripsGridField
)
);
return $fields;
}
}
TextBlocksStrip Class:
As soon as I try to add one of these via the gridfield on Page I get the error: Uncaught InvalidArgumentException: Invalid sort() column
<?php
class ComplexStrip extends DataObject {
private static $db = array(
'SortOrder' => 'Int'
);
private static $has_many = array(
'TextBlocks' => 'TextBlock'
);
//-------------------------------------------- CMS Fields
public function getCMSFields() {
$fields = parent::getCMSFields();
//---------------------- Main Tab
$textBlocks = GridField::create('TextBlocks', 'Text blocks', $this->TextBlocks(),
GridFieldConfig::create()->addComponents(
new GridFieldToolbarHeader(),
new GridFieldAddNewButton('toolbar-header-right'),
new GridFieldDetailForm(),
new GridFieldDataColumns(),
new GridFieldEditButton(),
new GridFieldDeleteAction('unlinkrelation'),
new GridFieldDeleteAction(),
new GridFieldOrderableRows('SortOrder'),
new GridFieldTitleHeader(),
new GridFieldAddExistingAutocompleter('before', array('Title'))
)
);
$fields->addFieldsToTab('Root.Main',
array(
$textBlocks
)
);
return $fields;
}
}
TextBlock Class:
<?php
class TextBlock extends DataObject {
private static $db = array(
'SortOrder' => 'Int'
);
private static $default_sort = 'Sort';
private static $has_one = array(
'TextBlocksStrip' => 'TextBlocksStrip'
);
}
If I remove the line new GridFieldOrderableRows('SortOrder') all works but is obviously not reorder-able.
I am pulling my hair out over this one. Makes no sense to me why this isn't working.
I have made a fairly elegant workaround.
The SortOrder could not be queried as the parent Object ComplexStrip doesn't exist at the time it is attempting to join the sorted table. So wrapping the GridFieldOrderableRows in an if to check if the parent object has been assigned an ID yet.
// Need to wait until parent object is created before trying to set sort order
if($this->ID) {
$textBlocks->addComponents(
new GridFieldOrderableRows('Sort')
);
}
After that everything works the same as before in the CMS. I will open an issue on the module now to see if there is a more permanent solution.
I had the same error as I was using $many_many instead of $has_many. I can see you used $has_many so wouldn't solve your issue but might help others encountering the same error message like I was.

Silverstripe. Modifying gridfieldconfig in modeladmin to add sortable headers

I'm using modeladmin to display a number of event DataObjects.
I've added a number of columns to the summary fields which the client wishes to be able to sort by. Currently, only title is sortable by default. Is it possible to modify gridfieldconfig in modeladmin? In particular to add fields to GridFieldSortableHeader?
Here is my Event dataobject with the summary fields that I need to be able to sort by in modeladmin:
......
static $summary_fields = array('Title', 'DescriptionSummary', 'EventStartDate', 'EventEndDate', 'EventVenue');
static $field_labels = array('DescriptionSummary' => 'Description', 'EventStartDate' => 'Start Date', 'EventEndDate' => 'End Date', 'EventVenue' => 'Venue');
private $widget;
//TO GET THE SUMMARY FIELD VALUES
public function getEventVenue(){
if ($eventVenue = $this->Venue()->Title) return $eventVenue;
return "No Venue specified";
}
public function getEventStartDate(){
if ($startDate = DataObject::get_one('CalendarDateTime', 'EventID = '.$this->ID)) return $startDate->StartDate;
return "No start dates specified";
}
public function getEventEndDate(){
if ($startDate = DataObject::get_one('CalendarDateTime', 'EventID = '.$this->ID)) return $startDate->EndDate;
return "No end dates specified";
}
....
and my event admin:
class EventAdmin extends ModelAdmin {
public static $managed_models = array('CalendarEvent', 'Venue', 'EventCategory');
static $url_segment = 'events';
static $menu_title = 'Events';
}
I've just added some info to doc.silverstripe.org on how to override the edit form and access the GridField within (link). The relevant bits (adapted to your use case):
class EventAdmin extends ModelAdmin {
// ...
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$gridField = $form->Fields()->fieldByName($this->sanitiseClassName($this->modelClass));
$gridField->getConfig()->getComponentByType('GridFieldSortableHeader')
->setFieldSorting(array(...));
return $form;
}
}
In case you're trying to sort by the CalendarDate relationship and the EventStartDate field, you'll generally have to override the results list in ModelAdmin, see docs.
While you can add the necessary join there (DataQuery->leftJoin), its not possible
to select additional columns in the query. So that would just allow you to sort by
EventStartDate by default, but not to re-sort the GridField via the UI.
Its a missing feature, we should really support "dot notations" in DataList->sort() out of the box.

Resources