I'm creating a custom theme in Drupal 8 and on the node.html.twig template I'm using {{content.field_type}} to display the link of the category which the current page is in to allow the user to link back to the list of pages in that category. Using this, the page renders:
Sub Cat Name
What do I need to do change this to:
My Custom Link
It's possible to change render arrays using preprocess functions, but in your case it's not a good idea. Link you are talking about is a result of rendering of field formatter. So, you just need another field formatter for your 'Type' field, instead of current 'Label' formatter.
Creating new formatter is quite easy(especially if you use EntityReferenceLabelFormatter as an example). Suppose you have a module called entity_reference_link_formatter. Then in the directory of this module create src/Plugin/Field/FieldFormatter folder and put there the following EntityReferenceLinkFormatter.php file:
<?php
/**
* #file
* Contains Drupal\entity_reference_link_formatter\Plugin\Field\FieldFormatter\EntityReferenceLinkFormatter
*/
namespace Drupal\entity_reference_link_formatter\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'entity reference link' formatter.
*
* #FieldFormatter(
* id = "entity_reference_link",
* label = #Translation("Link"),
* description = #Translation("Display the link to the referenced entity."),
* field_types = {
* "entity_reference"
* }
* )
*/
class EntityReferenceLinkFormatter extends EntityReferenceFormatterBase {
/**
* {#inheritdoc}
*/
public static function defaultSettings() {
return [
'text' => 'View',
] + parent::defaultSettings();
}
/**
* {#inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements['text'] = [
'#title' => t('Text of the link to the referenced entity'),
'#type' => 'textfield',
'#required' => true,
'#default_value' => $this->getSetting('text'),
];
return $elements;
}
/**
* {#inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = t('Link text: #text', ['#text' => $this->getSetting('text')]);
return $summary;
}
/**
* {#inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
if (!$entity->isNew()) {
try {
$uri = $entity->urlInfo();
$elements[$delta] = [
'#type' => 'link',
'#title' => t('!text', ['!text' => $this->getSetting('text')]),
'#url' => $uri,
'#options' => $uri->getOptions(),
];
if (!empty($items[$delta]->_attributes)) {
$elements[$delta]['#options'] += array('attributes' => array());
$elements[$delta]['#options']['attributes'] += $items[$delta]->_attributes;
// Unset field item attributes since they have been included in the
// formatter output and shouldn't be rendered in the field template.
unset($items[$delta]->_attributes);
}
}
catch (UndefinedLinkTemplateException $e) {
// This exception is thrown by \Drupal\Core\Entity\Entity::urlInfo()
// and it means that the entity type doesn't have a link template nor
// a valid "uri_callback", so don't bother trying to output a link for
// the rest of the referenced entities.
}
}
$elements[$delta]['#cache']['tags'] = $entity->getCacheTags();
}
return $elements;
}
}
After enabling this module (or after clearing the cache if this module was enabled earlier), you will have 'Link' formatter for all your 'Entity reference' fields, allowing you to customize link text just in the formatter settings.
Related
I'm trying to implement a hook_form_alter method to modify the behaviour of one field through ajax callback in the form display of node.
The idea is when I select one option from the select list field (field_country) modify the values of other field list (field_laws). Specificly, when I select one country, the hook method pass this value (current) through ajax callback to changeLawsData. This callback get one external service that returns one array of values filtered by the country selected previously.
The issue is inside of callback method, i can't access to $form and $form_state objects that contain the previous hook_form_alter.
My question is: Is posible to pass by arguments this objects to the callback? With this i could handler the state of form and its field, for example.
Something like this:
$form['field_country']['widget']['#ajax'] = array(
'callback' => [$this,'changeLawsData'],
'event' => 'change',
'disable-refocus' => FALSE,
**'arguments' = array($form, $form_state)**
);
Here is the full code of this implementation.
<?php
namespace Drupal\obs_urban_system\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\hook_event_dispatcher\HookEventDispatcherInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
/**
* Our event subscriber class.
*/
class NodeUrbanSystemFormAlterEventSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
HookEventDispatcherInterface::FORM_ALTER => 'hookFormAlter'
];
}
/**
* Implements hook_form_alter
*/
public function hookFormAlter($event) {
if($event->getFormId() == 'node_urban_system_edit_form') {
$form = $event->getForm();
$country = $form['field_country']['widget']['#default_value'];
$form['field_laws']['widget'][0]['value']['#options'] = \Drupal::service('custom_services.law')->getLawsByContent($country, 'country');
$form['field_law_articles']['widget'][0]['value']['#options'] = \Drupal::service('custom_services.law')->getLawArticlesByCountry($country);
$form['field_country']['widget']['#ajax'] = array(
'callback' => [$this,'changeLawsData'],
'event' => 'change',
'disable-refocus' => FALSE
);
$event->setForm($form);
}
}
/**
* #param $form
* #param \Drupal\Core\Form\FormStateInterface $form_state
* #return \Drupal\Core\Ajax\AjaxResponse
*/
function changeLawsData(&$form, FormStateInterface $form_state) {
<!--- HERE IM USING THE $form object --->
$country = $form['field_country']['widget']['#default_value'];
<!--- --->
$laws = \Drupal::service('custom_services.law')->getLawsByContent($country, 'country');
foreach ($laws as $key => $value) {
$option .= "<option value='" . $key . "'>" . $value . " </option>";
}
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#edit-field-laws-0-value', $option));
return $response;
}
}
Thanks to all very much.
You need to do all the form manipulation within the form_alter.
When the ajax callback is fired, the form will be rebuilt and the current values of the form will be available in the form_state.
Your ajax callback should only return what is needed on the front end, it should not actually manipulate the form array.
Here is an example with your code (example only, untested)
<?php
namespace Drupal\obs_urban_system\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\hook_event_dispatcher\HookEventDispatcherInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
/**
* Our event subscriber class.
*/
class NodeUrbanSystemFormAlterEventSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
HookEventDispatcherInterface::FORM_ALTER => 'hookFormAlter'
];
}
/**
* Implements hook_form_alter
*/
public function hookFormAlter($event) {
if($event->getFormId() == 'node_urban_system_edit_form') {
$form = $event->getForm();
$country = $form['field_country']['widget']['#default_value'];
// Get the form state object.
$form_state = $event->getFormState();
// Here we should check if a country has been selected.
$country = $form_state->getValue('country');
if ($country) {
// populate the options from service here.
$form['field_laws']['widget']['#options'] = \Drupal::service('custom_services.law')->getLawsByContent($country, 'country');
} else {
// Populate with default options.
$form['field_laws']['widget']['#options'] = [];
}
$form['field_law_articles']['widget'][0]['value']['#options'] = \Drupal::service('custom_services.law')->getLawArticlesByCountry($country);
$form['field_country']['widget']['#ajax'] = array(
'callback' => [$this,'changeLawsData'],
'event' => 'change',
'disable-refocus' => FALSE
);
$event->setForm($form);
}
}
/**
* #param $form
* #param \Drupal\Core\Form\FormStateInterface $form_state
* #return \Drupal\Core\Ajax\AjaxResponse
*/
function changeLawsData(&$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#edit-field-laws', $form['field_laws']));
return $response;
}
}
Please remember that the above is an example...
In D8, I want to show a custom form in the custom generated block. I've created the required custom module and assigned the block with a region. Nothing is showing into that region at front end if logged in as Administrator or Authenticated user. However, the form is coming if I see the front end as anonymous user. Below is my code. Need your help.
/src/Plugin/Block/DashBlock.php
namespace Drupal\dash\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'Dash' Block.
*
* #Block(
* id = "dash_block",
* admin_label = #Translation("Dash block"),
* category = #Translation("Dash"),
* )
*/
class DashBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function build() {
$form = \Drupal::formBuilder()->getForm('Drupal\dash\Form\WorkForm');
return $form;
}
}
/src/Form/WorkForm.php
/**
* #file
* Contains \Drupal\dash\Form\WorkForm.
*/
namespace Drupal\dash\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class WorkForm extends FormBase {
/**
* {#inheritdoc}
*/
public function getFormId() {
return 'dash_form';
}
/**
* {#inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['employee_name'] = array(
'#type' => 'textfield',
'#title' => t('Employee Name:'),
'#required' => TRUE,
);
$form['employee_mail'] = array(
'#type' => 'email',
'#title' => t('Email ID:'),
'#required' => TRUE,
);
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Register'),
'#button_type' => 'primary',
);
return $form;
}
/**
* {#inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
drupal_set_message($this->t('#emp_name ,Your application is being submitted!', array('#emp_name' => $form_state->getValue('employee_name'))));
}
}
The block visibility can be managed in adminitration views, go to Home > Administration > Structure > Block layout and select the region/block to edit and click Configure.
From there you can restrict the block visibility to specific user roles (content type, language, path as well). In your case it must be that "Anonymous user" is selected while you don't want it to.
Just unselect/select the proper roles for which your block to be restricted to, then save.
I have started looking into Magento 2 grid. I have developed one simple module but I didn't understand the structure of grid.
In Magento 1.9.X, the way was clear for adding grid but in Magento 2 there is structure is different. How do I add a grid in Magento 2?
In Magento 2, you can create a grid by XML (see here)
However, you can create a grid by PHP like Magento 1: Extending your grid class to "Magento\Backend\Block\Widget\Grid\Extended"
<?php
namespace Yourpackage\Yourmodule\Block\Adminhtml\Sample;
class Grid extends \Magento\Backend\Block\Widget\Grid\Extended
{
protected $_yourmodelFactory;
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Backend\Helper\Data $backendHelper,
\Yourpackage\Yourmodule\Model\YourmodelFactory $yourmodelFactory,
array $data = []
) {
parent::__construct($context, $backendHelper, $data);
$this->_yourmodelFactory = $yourmodelFactory;
}
protected function _construct()
{
parent::_construct();
$this->setId('sample_grid');
$this->setDefaultSort('id');
$this->setDefaultDir('DESC');
$this->setSaveParametersInSession(true);
}
protected function _prepareCollection()
{
$collection = $this->_yourmodelFactory->create()->getCollection();
$this->setCollection($collection);
return parent::_prepareCollection();
}
protected function _prepareColumns()
{
$this->addColumn(
'id',
[
'header' => __('ID'),
'align' => 'right',
'width' => '50px',
'index' => 'id',
]
);
// Some columns
return parent::_prepareColumns();
}
}
You can see more at: /vendor/magento/module-cms/Block/Adminhtml/Page/Grid.php.
1:Create controller Index.php
<?php
namespace Ced\Abhinay\Controller\Adminhtml\Account;
class Index extends \Magento\Backend\App\Action {
/**
* #var bool|\Magento\Framework\View\Result\PageFactory
*/
protected $resultPageFactory = false;
/**
* Index constructor.
* #param \Magento\Backend\App\Action\Context $context
* #param \Magento\Framework\View\Result\PageFactory $resultPageFactory
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\View\Result\PageFactory $resultPageFactory
)
{
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
}
public function execute()
{
$resultPage = $this->resultPageFactory->create();
$resultPage->getConfig()->getTitle()->prepend((__('Ced Abhinay')));
return $resultPage;
}
}
2:After that create layout file for this
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin- 2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Ced\Abhinay\Block\Adminhtml\Account\ListGrid" name="ced_custom_grid"/>
</referenceContainer>
</body>
</page>
3.After that create file ListGrid.php
<?php
namespace Ced\Abhinay\Block\Adminhtml\Account;
class ListGrid extends \Magento\Backend\Block\Widget\Grid\Container {
/**
* Class ListGrid extends parent constructor \Magento\Backend\Block \Widget\Grid
*/
protected function _construct()
{
$this->_controller = 'account_index';
$this->_blockGroup = 'Ced_Abhinay';
$this->_addButtonLabel = __('Ced Test');
parent::_construct();
}
}
4:Now finally create your Grid.php
<?php
namespace Ced\Abhinay\Block\Adminhtml\Account\Grid;
class Grid extends \Magento\Backend\Block\Widget\Grid\Extended {
/** #var \Ced\Abhinay\Model\ListModel */
protected $listModelData;
/**
* Grid constructor.
* #param \Magento\Backend\Block\Template\Context $context
* #param \Magento\Backend\Helper\Data $backendHelper
* #param \Ced\Abhinay\Model\ListModel $listModelData
* #param array $data
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Backend\Helper\Data $backendHelper,
\Ced\Abhinay\Model\ListModel $listModelData,
array $data = []
) {
parent::__construct($context, $backendHelper, $data);
$this->listModelData = $listModelData;
}
protected function _construct()
{
parent::_construct();
$this->setId('list_grid');
$this->setDefaultSort('list_id');
$this->setDefaultDir('DESC');
$this->isAjax('true');
}
protected function _prepareCollection()
{
$collection = $this->listModelData->getCollection();
$this->setCollection($collection);
return parent::_prepareCollection();
}
protected function _prepareColumns()
{
$this->addColumn(
'post_id',
[
'header' => __('ID'),
'sortable' => true,
'index' => 'post_id',
'type' => 'number',
'header_css_class' => 'col-id',
'column_css_class' => 'col-id'
]
);
$this->addColumn(
'title',
[
'header' => __('Name'),
'index' => 'name',
'header_css_class' => 'col-name',
'column_css_class' => 'col-name'
]
);
$this->addColumn(
'position',
[
'header' => __('Position'),
'name' => 'position',
'width' => 60,
'type' => 'number',
'validate_class' => 'validate-number',
'index' => 'position',
'editable' => true,
]
);
return parent::_prepareColumns();
}
}
The best practice is to create all grids via UI components (xml).
Look into Magento_Catalog module and find product_form.xml.
Now the preferred way of adding grid inside adminhtml is with ui components
Reason why it's the best way now is because you can use a lot of magento 2 backend functionality when adding it as ui component.
However there are multiple ways to add this.
Not to repeat the answer and tons of code in stackoverflow i found a mageplaza explination, that explains the creation of the grid.
https://www.mageplaza.com/magento-2-module-development/create-admin-grid-magento-2.html
You can also refer to magento 2 documentation to take a look on additional components you can use in you ui component:
https://devdocs.magento.com/guides/v2.0/ui-components/ui-component.html
There are multiple existing components you can you in grid and you can create your own. Aldo complex they do offer big amount of flexibility when setuped. And when you do create a couple you will understand how they function and will be able to work with them with ease.
I'm learning SilverStripe by creating a small website that lets the user manage their fragrances (i.e. perfumes/colognes). The user adds ingredients (that are used in the fragrances they have), then adds their fragrances, at which point they choose which ingredients are in the fragrance they're adding.
I've created the Ingredient and Fragrance classes which both extend DataObject. I've also created the IngredientsPage page which lets the user add/edit/delete ingredients (made up of a name and description) and lists all of the ingredients added so far, and this page is fully functional. I'm now trying to create the FragrancesPage page which will let the user add/edit/delete fragrances (made up of a name, description and ingredients) and list all the ones added so far, but I'm having some trouble.
The only way I know of to create the relationship between a Fragrance and Ingredients (one fragrance has many ingredients, and one ingredient belongs to many fragrances) is using a GridField (if there's a better way, let me know!), as this is what the SilverStripe tutorials get you to do (although in the tutorial it's for the CMS rather than the front-end). However, as soon as I try to add a GridField into the mix, I just get taken to an error page that says "Server Error: Sorry, there was a problem with handling your request.".
My code is as follows.
Ingredient.php:
<?php
class Ingredient extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $belongs_many_many = array(
'Fragrances' => 'Fragrance'
);
}
?>
Fragrance.php:
<?php
class Fragrance extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $many_many = array(
'Ingredients' => 'Ingredient'
);
}
?>
FragrancesPage.php:
<?php
class FragrancesPage extends Page {
private static $icon = 'cms/images/treeicons/reports-file.png';
private static $description = 'Fragrances page';
}
class FragrancesPage_Controller extends Page_Controller {
private static $allowed_actions = array('FragranceAddForm');
function FragranceAddForm() {
$config = GridFieldConfig_RelationEditor::create();
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Name' => 'Name',
'Ingredient.Name' => 'Ingredient'
));
$fragrances_field = new GridField(
'Ingredients',
'Ingredient',
$this->Ingredients(),
$config
);
$fields = new FieldList(
new TextField('Name', 'Fragrance Name'),
new TextareaField('Description', 'Fragrance Description'),
$fragrances_field
);
$actions = new FieldList(
new FormAction('doFragranceAdd', 'Add Fragrance')
);
$validator = new RequiredFields('Name', 'Description');
return new Form($this, 'FragranceAddForm', $fields, $actions, $validator);
}
public function doFragranceAdd($data, $form) {
$submission = new Fragrance();
$form->saveInto($submission);
$submission->write();
return $this->redirectBack();
}
public function FragranceList() {
$submissions = Fragrance::get()->sort('Name');
return $submissions;
}
}
?>
If I remove everything from FragrancesPage.php relating to the GridField, the page works fine. I just can't seem to get the GridField working, and don't know of any other way to create the relationship between Fragrances and Ingredients on the front-end. If the code for IngredientsPage.php will be helpful also, let me know and I'll add it.
my guess is that you have error reporting turned off, and that is why you only see such a meaning less error message.
you should turn on display_errors and error_reporting in your php.ini, .htaccess or _ss_environment.php (IMPORTANT: setting it in _config.php will NOT work, as it gets overwritten by the error handler)
the problem I see in your code is that trying to use $this->Ingredients() on FragrancesPage, but as far as I can see only the class Fragrances has a method Ingredients (method is 'magically' created for the many_many relation).
also, I think your setDisplayFields()
so basically you need to use $fragrance->Ingredients() instead of $this->Ingredients().
but that leads us to the next problem: you don't have a fragrance yet.
unfortunately, at this time, GridField only works if you already have an object. this means you have to split it into two forms or use an alternative.
Option 1: use CheckboxSetField to manage the many_many relation.
this will not allow creating Ingredients on the fly, it will only give you check boxes that you can tick to link the items.
public function FragranceAddForm() {
$fragrances_field = new CheckboxSetField('Ingredients', 'Ingredient', Ingredient::get()->map());
$fields = new FieldList(
new TextField('Name', 'Fragrance Name'),
new TextareaField('Description', 'Fragrance Description'),
$fragrances_field
);
$actions = new FieldList(
new FormAction('doFragranceAdd', 'Add Fragrance')
);
$validator = new RequiredFields('Name', 'Description');
return new Form($this, __FUNCTION__, $fields, $actions, $validator);
}
public function doFragranceAdd($data, $form) {
$submission = new Fragrance();
$form->saveInto($submission);
$submission->write();
return $this->redirectBack();
}
Option 2: use GridField in a second form
this will allow creating Ingredients on the fly, but is a bit more work. and you might run into some troubles with GridField as it is not fully tested in the frontend yet.
(there is a recent question where I wrote a bit about GridField problems in frontends https://stackoverflow.com/a/22059197/1119263)
I guess this place is as good as any to finally write a tutorial/working example for frontend GridFields.
I took the liberty of refactoring your code a bit to include a edit functionality, its much nicer to have the GridField on the edit page than on a separate form.
As mentioned before, the GridField is not working that well in the frontend, there is a module to ease the pain, but it will still be rough around the edges and will require you to do some styling to make it look pretty.
Find the module on Packagist or GitHub
(you will need t
class Ingredient extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $belongs_many_many = array(
'Fragrances' => 'Fragrance'
);
public function getCMSFields() {
// fields used by the GridField, don't let the CMSFields mislead you
return new FieldList(
TextField::create('Name', 'Name'),
TextAreaField::create('Description', 'Description')
);
}
}
class Fragrance extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $many_many = array(
'Ingredients' => 'Ingredient'
);
}
/**
* Form in a separate class, so we can reuse it.
* #param Controller $controller
* #param string $name
* #param Null|Fragrance $fragrance Either null to create a new one, or pass an existing to edit it and add Ingredients
* #return Form
*/
class FragranceForm extends Form {
public function __construct($controller, $name, $fragrance = null) {
if ($fragrance && $fragrance->isInDB()) {
// we can only use a GridField if the object exists and has already been saved
// gridfield needs jQuery
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.min.js');
// ensure we don't have 2 versions of jQuery
Requirements::block(THIRDPARTY_DIR . '/jquery/jquery.js');
$config = FrontEndGridFieldConfig_RelationEditor::create();
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Name' => 'Name',
'Description' => 'Description',
));
$ingredientField = new FrontEndGridField(
'Ingredients',
'Ingredient',
$fragrance->Ingredients(),
$config
);
} else {
$ingredientField = new LiteralField('Ingredients', '<p>Ingredients can be added after saving</p>');
}
$fields = new FieldList(
new HiddenField('ID', ''),
new TextField('Name', 'Fragrance Name'),
new TextareaField('Description', 'Fragrance Description'),
$ingredientField
);
$actions = new FieldList(
new FormAction('doFragranceSave', 'Save Fragrance')
);
$validator = new RequiredFields('Name', 'Description');
// populate the fields (ID, Name and Description) with the values from $fragrance. This does not effect the GridField
if ($fragrance && $fragrance->exists()) {
$fields->fieldByName('ID')->setValue($fragrance->ID);
$fields->fieldByName('Name')->setValue($fragrance->Name);
$fields->fieldByName('Description')->setValue($fragrance->Description);
// there is actually a method for that, but we can't use it here,
// because fields are not set yet. we could do it after __construct, but then we would
// overwrite things set by the error handler, so lets just do it by hand
// $this->loadDataFrom($fragrance);
}
parent::__construct($controller, $name, $fields, $actions, $validator);
}
public function doFragranceSave($data, $form) {
if (isset($data['ID']) && $data['ID']) {
$id = (int)$data['ID'];
$fragrance = Fragrance::get()->byID($id);
}
if (!isset($fragrance) || !$fragrance || !$fragrance->exists()) {
// if the ID was invalid or we don't have one, create a new Fragrance
$fragrance = new Fragrance();
}
$form->saveInto($fragrance);
$fragrance->write();
// redirect to the edit page.
$controller = $this->getController();
$editLink = $controller->EditLink($fragrance->ID);
return $controller->redirect($editLink);
}
}
class FragrancesPage extends Page {
}
class FragrancesPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'edit',
'AddForm',
'EditForm',
);
/**
* the default action
* #return ViewableData_Customised
*/
public function index() {
// $this->customise() lets you overwrite variables that you can use in the template later.
return $this->customise(array(
// set the AddForm to $Form instead of $AddForm, this way you can use $Form in template and can reuse the template
'Form' => $this->AddForm(),
));
}
/**
* edit action to edit an existing Fragrance
* links will look like this /FragrancesPage/edit/$ID
*
* #param SS_HTTPRequest $request
* #return SS_HTTPResponse|ViewableData_Customised
*/
public function edit(SS_HTTPRequest $request) {
$id = (int)$request->param('ID');
$fragrance = Fragrance::get()->byID($id);
if (!$fragrance || !$fragrance->exists()) {
// fragrance not found? display a 404 error page
return ErrorPage::response_for(404);
}
// now that we have a $fragrance, overwrite EditForm with a EditForm that contains the $fragrance
$form = $this->EditForm($fragrance);
$return = $this->customise(array(
// also overwrite Title and Content, to display info about what the user can do here
// if you don't overwrite that, it will display the Title and Content of the page
'Title' => 'Edit: ' . $fragrance->Name,
'Content' => '<p>you are editing an existing fragrance</p>',
// set the Form to $Form instead of $EditForm, this way you can use $Form in template and can reuse the template
'Form' => $form,
));
// per default SilverStripe will try to use the following templates: FragrancesPage_edit.ss > FragrancesPage.ss > Page.ss
// if you want to use a custom template here, you can specify that with ->renderWith()
// but you probably won't need that anyway
// $return = $return->renderWith(array('MyCustomTemplateName', 'Page'));
return $return;
}
public function AddForm() {
return new FragranceForm($this, __FUNCTION__);
}
public function EditForm($fragranceOrRequest = null) {
// unfortunately, GridField / FormFields in general are a bit clumsy and do forget what item they where
// suppose to edit, so we have to check what $fragranceOrRequest is and set/get the fragrance to/from session
if ($fragranceOrRequest && is_a($fragranceOrRequest, 'Fragrance')) {
$fragrance = $fragranceOrRequest;
Session::set('FragrancesPage.CurrentFragrance', $fragrance->ID);
} else {
$fragrance = Fragrance::get()->byID(Session::get('FragrancesPage.CurrentFragrance'));
}
if (!$fragrance || !$fragrance->exists()) {
// that's bad, some error has occurred, lets display an ugly 404 page
return $this->httpError(404);
}
return new FragranceForm($this, __FUNCTION__, $fragrance);
}
public function EditLink($ID) {
return $this->Link("edit/$ID");
}
}
I know that's a lot to take in as someone still learning SilverStripe, if you have any questions, feel free to comment or just poke me on IRC
I have a form where I can fill in my euros, my entity only knows cents and is a integer.
So I want to create (not sure if i'm using the right method) form transformer.
What I do:
class EuroTransformer implements DataTransformerInterface
{
public function transform($euro)
{
return $euro * 100;
}
public function reverseTransform($euro)
{
return $euro / 100;
}
}
form:
->add('price', 'money', array(
'attr' => array(
'style' => 'width: 70px;'
)
))
->addModelTransformer($euroTransformer)
But i'm getting the next message:
The form's view data is expected to be an instance of class Entity\InvoiceRule, but is a(n) integer. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) integer to an instance of Entity\InvoiceRule.
And yes I have already a data_class in my default options.
How to solve my problem?
using symfony2 2.2
Sf2 MoneyType handles this case !
->add('price', 'money', array(
'divisor' => 100,
'attr' => array(
'style' => 'width: 70px;'
)
))
You need to return an object in your reverseTransform method:
/**
* #param int $cents
*
* #return InvoiceRule
*/
public function reverseTransform($cents)
{
$euro = new InvoiceRule();
$euro->setValue($cents / 100);
return $euro;
}
And your transform method must transform an object into a number:
/**
* #param InvoiceRule $euro
*
* #return int
*/
public function transform($euro)
{
return $euro->getValue() * 100;
}
See the documentation for more examples.