I have an Order Entity.
I have a Status Entity that contains a number of status options, ie. Submitted, Invoiced, Paid, Shipped (id 1-4). I have a page that I want to list a number of buttons on it each corresponding to a certain status as mentioned above. Basically the reason for the multiple buttons is that I want the user to be able to basically skip certain status records by just clicking the one they want... I have some logic that will determine what buttons to present however my problem is being able to tell what button was clicked when the form is submitted.
From the docs the only way I know to see if a button was clicked is to use:
$form->get('buttonName')->isClicked();
The way i am thinking about this right now is that my buttons will be named by the statusId that it corresponds to. ie: $form->add('status1', SubmitType::class) # being the ID of the status record.
this problem with this is that the buttons generated on the page will be dynamic. So it would seem like at this point I would need to query my DB for all the statuses and iterate through all the possibilities:
foreach ($statuses as $status)
{
if($form->get('status' . $status->getId())->isClicked()){
{
//Do Something
break;
}
}
I feel like there has to be a better way... IE: $form->getSubmitButtonName() which would return "status#" then I could just do a substr() to get the ID out of the name..
I would suggest using a dropdown menu instead of 4 buttons, and just a simple submit button to save it. It should fix your problem, and look a lot better then having 4 identical buttons that just save different values, example code below:
The Entity should look something like this: (us the toString method to display the actual names instead of (0, 1, 2, 3)
abstract class OrderStatus{
const submitted = 0;
const Ivoiced = 1;
const Paid = 2;
const Shipped = 3;
}
/**
* #ORM\Column(type="integer")
*/
protected $order_status; // see OrderStatus enum above
/**
* Set orderStatus
*
* #param integer $orderStatus
*
* #return Order
*/
public function setOrderStatus($orderStatus)
{
$this->order_status = $orderStatus;
return $this;
}
/**
* Get orderStatus
*
* #return integer
*/
public function getOrderStatus()
{
return $this->order_status;
}
public function getOrderStatusToString()
{
switch ($this->getOrderStatus()) {
case 0: return "submitted"; break;
case 1: return "invoiced"; break;
case 2: return "paid"; break;
case 3: return "Shipped"; break;
}
}
add this in the controller:
//the dropdown menu made in form (can be put in a formType instead of controller if you wish)
$form = $this->createFormBuilder(your.entity.name)
->add('orderStatus', 'choice', array(
'choices' => array(
'submitted' => 0,
'invoiced' => 1,
'paid' => 2,
'Shipped' => 3,
),
'choices_as_values' => true,
'label'=>false
))
$form->add('save', 'submit', array('label' => 'Save'));
$form = $form->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em=$this->getDoctrine()->getManager();
$em->persist(your.entity.name);
$em->flush();
}
in your html file smth like: (with any css you want)
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
This all together (with some tweaks) should do the trick
Related
I have created a data transformation string to date, but I really wanted to try and extend the DateType as it has all the logic for handling dates, has anyone done this before or should I just use a DataTransformer (which is what I have implemented)?
I tried doing the following:
Class HiddenDateType extends DateType {
public function getParent() {
return HiddenType::class;
}
}
But this didn't work, when I looked into the DateType Class it was a little more complicated than I was expecting and didn't know where to hook into the returned type.
I found a few solutions but most of them where pretty hacky, ie; Hiding the entire field via CSS, or changing the type inside the twig template.
This is my transformer, it's VERY redemantry, its frustrating that there is code RIGHT inside the DateType but my skills are lacking :(
class StringToDateTransformer implements DataTransformerInterface
{
/**
* #param \DateTime|null $value
* #return string
*/
public function transform($value)
{
if(null === $value) {
return '';
}
return $value->format('Y-m-d');
}
/**
* #param string $value
* #return \DateTime
*/
public function reverseTransform($value)
{
try {
return new \DateTime($value);
} catch (\Exception $e) {
throw new TransformationFailedException(sprintf(
'Invalid Format for %s format yyyy-mm-dd',
$value
));
}
}
}
On the actual rendered page there is a single div which has a daterangepicker javascript event (on click) is attached to it, this event is triggered when clicked which allows the user to select a start and end date.
The javascript will then populate two hidden DateType fields (startDate, endDate) within the form.
This is the div that the daterangepicker picker is attached to.
<div id="selectedRange">
<i class="fa fa-calendar"></i>
<span></span> <i class="fa fa-caret-down"></i>
</div>
You should be able to do this in your formType class buildForm() method
// ./src/form/yourType.php
$builder->add('hidden_date_field_name', DateType::class, [
'attr' => [
'hidden' => 'hidden',
'disabled' => 'disabled',
],
]);
You should be fine using a DateType or DateTimeType if you do not plan to use something like bootstrap timepicker or any other JS modification of your input.
However, if that's the case and you want to make a better looking date input, the simple way to do it is to use it as a string like so :
$builder->add('date', TextType::class, array(
'required' => true,
'label'=>'Date',
'attr' => array(
'class' => 'datetimepicker',
'data-provide' => 'datepicker',
'data-format' => 'dd-mm-yyyy HH:ii'
),
));
And be sure to add your ModelTransformer to the input in your FormType like so:
$builder->get('date')
->addModelTransformer(new StringToDateTransformer());
Note that you can specify the date format for your js component in the input attribute.
I am attempting to write a module that adds a button to a node type that, when pressed, will change a value of a field in that node and submit the changes. Everything seems to be working, as the button appears correctly and the node submits when it's pressed, but the value of the field remains unchanged. I feel like I'm missing something obvious.
<?php
function iu_buttons_node_view($node, $view_mode, $langcode) {
if ($node->type == 'billing_entry') {
if ($node->field_status['und'][0]['value'] == 'open') {
$form = drupal_get_form('submit_button_form');
$node->content['submit_button'] = $form;
}
}
}
function submit_button_form($form, &$form_submit) {
$form['submit'] = array(
'#type' => 'button',
'#value' => ('Submit'),
'#submit' => array('submit_button_form_submit'),
);
return $form;
}
function submit_button_form_submit($form, &$form_state) {
$node->field_status['und'][0]['value']['#value'] = 'submitted';
}
It's probably worth noting that the field I'm trying to change is a select list. Should I be using a different function than hook_form_submit?
I am assuming you are writing this code in a custom module named iu_buttons and you want to display this button in the node page, not in the node edit form.
In this case, the problem is that you never saved the node in your submit function. Here is a version of your submit function that will save the node.
function submit_button_form_submit($form, &$form_state) {
$node = menu_get_object(); //get the current node
$node->field_status['und'][0]['value']['#value'] = 'submitted';
node_save($node); // save the changed node
}
I think you may be interested in saving only the field, without the need to save the whole node. In this case, you might use the following:
function submit_button_form_submit($form, &$form_state){
$node = new stdClass(); // Create empty object
$node->nid = intval(args(1)); // Include the nid so that Drupal saves the new value to the correct node
$node->field_status['und'][0]['value']['#value'] = 'submitted';
field_attach_update('node', $node); // Save the field
}
In the list field you can make a field editable by setting the attribute "editable" to "true" in the configureListFields action. Is it possible (with onboard sonata admin tools) to make a field editable that contains multiple values as in a one-to-many relation?
Example:
I have a list of pupils listed in the list view. Every pupil has multiple classes listet in the classes column of the pupils list view. Via click on the classes I want a popover open (like it works with a normale string) with a suggest field like you can have it in the edit view.
Using the properties like in the configFormFields action doesn't work:
$listMapper->add(
'classes',null, array(
'editable' => true,
'type' => 'sonata_type_model_autocomplete',
'multiple' => true,
'property' => 'name'
)
);
That snippet is written inside the PupilsAdmin class in the configureListFields action.
Is it possible or do I have to create a custom template?
The documentation doesn't point me in the right direction: https://sonata-project.org/bundles/admin/2-2/doc/reference/field_types.html
If i understand you right, you want to edit a one-to-many relation inline in a list view of sonata. As far as i know, thats only possible for simple types like text, int or choices and so on. They point it out on no 18. in your link
Theses types accept an editable parameter to edit the value from within the list action. This is currently limited to scalar types (text, integer, url...).
So related objects can not be in that list, merely their scalar properties. For all other things you have to write your own template ...
I don't know what you want to achieve with this suggested list, but for me it makes no sense to edit a one-to-many property in the list view like its done in the edit view.
You just need to create new type. Something like "entity"
'header_class' => 'col-lg-1',
'class' => Employee::class,
'editable' => true,
])
Next step is to override fixFieldDescription method in listBuilder and handle it
class EntityListBuilder extends ListBuilder
{
/**
* #var Registry
*/
private $doctrine;
/**
* #param AdminInterface $admin
* #param FieldDescriptionInterface $fieldDescription
*/
public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription)
{
parent::fixFieldDescription($admin, $fieldDescription);
if ($fieldDescription->getType() === 'entity') {
$class = $fieldDescription->getOption('class');
if (!$class) {
throw new RuntimeException("Type entity must contain 'class' argument");
}
$objects = $this->doctrine->getRepository($class)->findAll();
$choices = [];
foreach ($objects as $object) {
$choices[$object->getId()] = $object->__toString();
}
$fieldDescription->setOption('choices', $choices);
$fieldDescription->setType('choice');
}
}
/**
* #param Registry $doctrine
*/
public function setDoctrine(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
/**
* #param string $type
*
* #return string
*/
private function getTemplate($type)
{
return $this->templates[$type] ?? '';
}
Now, you have to override template for your "entity" type
{% extends '#SonataAdmin/CRUD/list_choice.html.twig' %}
{% set value = admin.id(value) %}
It's need to set already chosen value to select box
Okay, last thing is to add our type to xeditable types of Twig
Add it to OverrideServiceCompilerPass :
$definition = $container->getParameter('sonata.admin.twig.extension.x_editable_type_mapping');
$definition['entity'] = 'select';
$container->setParameter('sonata.admin.twig.extension.x_editable_type_mapping', $definition);
And the last one just match your type with template
templates:
types:
list:
...
entity: AppBundle:CRUD:list_entity.html.twig
Now you ready to edit it inline :)
Well, I have a fairly basic problem with Sonata Admin in my Symfony2 project.
I have a "products" list view with every product sold on my web store. On the top right "actions" menu, I have the default actions, with a single action named "Add new".
I just want to add more actions next to "Add new": custom actions like "remove promo prices from all products", or "remove all products evaluations".
I don't want a "batch" action, I want a "global" action leading to a custom DB query.
All I find in the doc is related to batch actions or "single line action". Is there a way to do what I want ?
Thank you for your help !
Create and configure a custom admin extension and override the configureActionButtons(AdminInterface $admin, $list, $action, $object) method to add custom actions:
use Sonata\AdminBundle\Admin\AdminExtension;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Route\RouteCollection;
class CustomGlobalActionsExtension extends AdminExtension
{
public function configureActionButtons(AdminInterface $admin, $list, $action, $object)
{
return array_merge($list, [
['template' => 'admin/custom_action.html.twig']
]);
}
public function configureRoutes(AdminInterface $admin, RouteCollection $collection)
{
$collection->add('custom_action', $admin->getRouterIdParameter().'/custom_action');
}
}
{# app/Resources/views/admin/custom_action.html.twig #}
<a class="btn btn-sm" href="{{ admin.generateObjectUrl('custom_action', object) }}">Custom Action</a>
See also https://sonata-project.org/bundles/admin/2-3/doc/cookbook/recipe_custom_action.html
Is syntax changed a little bit and it is important to call the parent method:
/**
* #param $action
* #param null|object $object
* #return array
*/
public function configureActionButtons($action, $object = null)
{
$buttonList = parent::configureActionButtons($action, $object);
$buttonList['create_custom'] = [
'template' => 'admin/action/button.html.twig'
];
unset($buttonList['not_wanted']);
return $buttonList;
}
I'm writing a custom Drupal 7 module which will completely override the search page and search method for a website. Here is what I have so far:
/**
* Custom search.
*/
function mymodule_search_page() {
drupal_add_css('css/search.css');
// Perform a search (not important how)
$result = do_custom_search('foo');
return '<p>Results:</p>';
}
Now, as you can see, it's not complete. I don't know how to properly return structured HTML from this. How would I go about using Drupal's built-in template system to render the results?
you have to make use of drupal inbuilt functions. i hope you are looking for something like this http://api.drupal.org/api/drupal/includes!common.inc/function/drupal_render/7
This is what I ended up doing:
/**
* Implements hook_menu().
*/
function mymodule_search_menu() {
$items = array();
$items['search'] = array('page callback' => 'mymodule_search_page',
'access callback' => TRUE);
return $items;
}
/**
* Mymodule search page callback.
*/
function mymodule_search_page() {
$variables = array();
// Add stuff to $variables. This is the "context" of the file,
// e.g. if you add "foo" => "bar", variable $foo will have value
// "bar".
...
// This works together with `mymodule_search_theme'.
return theme('mymodule_search_foo', $variables);
}
/**
* Idea stolen from: http://api.drupal.org/comment/26824#comment-26824
*
* This will use the template file custompage.tpl.php in the same
* directory as this file.
*/
function mymodule_search_theme() {
return array ('mymodule_search_foo' =>
array('template' => 'custompage',
'arguments' => array()));
}
Hope this helps someone!