Silverstripe - Repeating / Reusable grouped fields - silverstripe

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>

Related

Zend Framework 3 Form Fieldset

Hy,
I'm really new to Zend-Framework 3 and I'm practicing OOP, I can't find a simple explanation/tutorial on making a Zend Form with a fieldset and legend. Basically I'm trying to create this in HTML:
<form name="my_name">
<fieldset>
<legend>My legend value</legend>
<input type="checkbox" name="name_1" value="value_1">Value 1</input>
<input type="checkbox" name="name_2" value="value_2">Value_2</input>
<input type="checkbox" name="name_3" value="value_3">Value_3</input>
</fieldset>
<input type="button" value="Get values" id="btn"/>
</form>
I checked the official documentation about Zend Forms and Collections and Fieldsets, but it's really confusing me. Any help would be greatly appreciated.
First, I am sorry as it is going to be a bit long one. But this would describe the form in action. So be patient please!
Assuming you are known to ZF3 default Application module. Some folders are created in the Application module for separation of each element. You need to create them as follows.
Let's get started by creating your fieldsets first. Zend\Form\Fieldset component represents a reusable set of elements and is dependent on Zend\From\Form component. This means you need to attach this to Zend\Form\Form.
module/Application/src/Form/Fieldset/YourFieldset.php
<?php
namespace Application\Form\Fieldset;
use Zend\Form\Element;
use Zend\Form\Fieldset;
class YourFieldset extends Fieldset
{
public function __construct($name = null)
{
parent::__construct('your-fieldset');
$this->add([
'name' => 'name_1',
'type' => Element\Checkbox::class,
'options' => [
'label' => 'Value 1',
'use_hidden_element' => true,
'checked_value' => 'yes',
'unchecked_value' => 'no',
],
'attributes' => [
'value' => 'no',
],
]);
// Creates others as your needs
}
}
Now we would create the form using Zend\From\Form attaching the fieldset created from Zend\From\Fieldset.
module/Application/src/Form/YourForm.php
<?php
namespace Application\Form;
use Application\Form\Fieldset\YourFieldset;
use Zend\Form\Form;
class YourForm extends Form
{
public function __construct($name = null)
{
parent::__construct('your-form');
$this->add([
// This name will be used to fetch each checkbox from
// the CheckboxFieldset::class in the view template.
'name' => 'fieldsets',
'type' => YourFieldset::class
]);
$this->add([
'name' => 'submit',
'attributes' => [
'type' => 'submit',
'value' => 'Get Values',
'class' => 'btn btn-primary'
],
]);
}
}
Our from is almost ready. We need to make it serviceable if we want it to be used in any action of a controller. So let's do that.
Update your module config file. If service_manager key does not exist then add the following snippet of code, otherwise, update only factories and aliases key as the following.
Fix namespaces in module config file.
module/Application/config/module.config.php
'service_manager' => [
'factories' => [
// Form service
Form\YourForm::class => Zend\ServiceManager\Factory\InvokableFactory::class,
// Other services
],
'aliases' => [
// Make an alias for the form service
'YourForm' => Form\YourForm::class,
],
],
Now the form is ready to be used. This needs to be injected into our controller. As I am working on Application module, I would inject the form into the IndexController::class's constructor. Then we would be using that form inside IndexController::fieldsetAction() method.
module/Application/src/Controller/IndexController.php
<?php
namespace Application\Controller;
use Zend\Form\FormInterface;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
protected $YourForm;
public function __construct(FormInterface $YourForm)
{
$this->YourForm = $YourForm;
}
public function fieldsetAction()
{
$request = $this->getRequest();
$viewModel = new ViewModel(['form' => $this->YourForm]);
if (! $request->isPost()) {
return $viewModel;
}
$this->YourForm->setData($request->getPost());
if (! $this->YourForm->isValid()) {
return $viewModel;
}
$data = $this->YourForm->getData()['fieldsets'];
echo '<pre>';
print_r($data);
echo '</pre>';
return $viewModel;
}
}
As this controller is taking argument in its constructor, we need to create a factory for this controller (a factory creates other objects).
module/Application/src/Factory/Controller/IndexControllerFactory.php
<?php
namespace Application\Factory\Controller;
use Application\Controller\IndexController;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class IndexControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// We get form service via service manager here
// and then inject controller's constructor
$YourForm = $container->get('YourForm');
return new IndexController($YourForm);
}
}
Once again, we need to update the module config file. We would add this time the factory under controllers key as follows
'controllers' => [
'factories' => [
Controller\IndexController::class => Factory\Controller\IndexControllerFactory::class,
],
],
At the end, echo the form in the view template as follows:
module/Application/view/application/index/fieldset.phtml
<h1>Checkbox Form</h1>
<?php
$form = $this->form;
$form->setAttribute('action', $this->url());
// Here is the catch, remember this name from the CheckboxForm::class
$fieldset = $form->get('fieldsets');
$name_1 = $fieldset->get('name_1');
$name_2 = $fieldset->get('name_2');
$name_3 = $fieldset->get('name_3');
$submit = $form->get('submit');
$submit->setAttribute('class', 'btn btn-primary');
$form->prepare();
echo $this->form()->openTag($form);
?>
<fieldset>
<legend>My legend value</legend>
<?= $this->formElement($name_1) ?>
<?= $this->formLabel($name_1) ?>
<?= $this->formElement($name_2) ?>
<?= $this->formLabel($name_2) ?>
<?= $this->formElement($name_3) ?>
<?= $this->formLabel($name_3) ?>
<?= $this->formSubmit($submit) ?>
</fieldset>
<?php
echo $this->form()->closeTag();
Hope this would help you!
Actually the example you are looking for is in "collections" part of zend form. Its not the exact one but kinda like.
Here you are a little example. I ignored namespaces and hope so there's no typo :)
class myFieldset extends Fieldset {
public function init(){
$this
->add([
'name' => 'name_1,
'type' => 'text',
])
->add([
'name' => 'name_2,
'type' => 'text',
])
->add([
'name' => 'name_3,
'type' => 'text',
]);
}
}
class MyForm extends Form {
public function init(){
$this->add([
'type' => myFieldset,
'name' => 'fieldset'
])->add([
'type' => 'button',
'name' => 'action'
]);
}
}
And in view file;
<?=$this-form($form);?>

SilverStripe - Adding 2 TreeDropdownFields, only one works

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.

Does silverstripe have recursive data relationships?

Does silverstripe have recursive data relationships? I tried to implement it and it gives no errors but the page is blank on modeladmin.
Example has_one recursive relation on Product to itself:
class Product extends DataObject {
private static $db = array(
'Name' => 'Varchar',
'ProductCode' => 'Varchar',
'Price' => 'Currency'
);
private static $has_one = array(
'Product' => 'Product'
);
}
Yes, this is possible.
There can be problems when doing this with Many_Many relationships, though.
My answer would be "no" for this done in this way. Where I've need this in the past I've created the "has one" as an "int" in the db array...
class Product extends DataObject {
private static $db = array(
'Name' => 'Varchar',
'MyProductID' => 'Int',
);
}
this means I've had to add casting for the summary field, custom scaffolding for the search fields and in the getCMSFields to replaceField the int field for a DropdownField to select a product.

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
);

Symfony 1.4. How to add attributes of fields to the embedded form?

I embed one form to another. I need to add HTML attributes to the fields embedded form.
Trying to do so:
$this->widgetschema['email'] = new sfWidgetFormInputText(array(), array('class' => 'email'));
but it does not work.
Did you try it this way?
creating a class
class YourForm extends sfForm {
$array= array('array');
$this->setWidgets(array(
'field1' => new sfWidgetFormSelect(array('choices' => $array), array('placeholder' => 'field1', 'required' => 'true', 'data-empty' => 'U did not enter field1!')),
'email_address' => new sfWidgetFormInputText(array('type' => 'email'), array())
));
}
You can even change the type in the first array. Then include your form in the actions by calling it like
$this->form = new YourForm();
Then echo your form in the template:
<?php echo $form; ?>
I hope this helps. I use it like this as well, to add extra attributes to use in javascript.

Resources