Sonata Admin Bundle clickable Field - mongodb

I'll describe a little bit the architecture of my models to understand my problem: I'm developing a Symfony2 web-app.
And I installed the sonataMongoDB Admin Bundle to create my Admin part.
The application is an online Quizzer in fact I have a document User which reference many documents Quizz. when I'm displaying the users list I need that the quiz field become clickable to go inside the quiz and see the results.
Here is the code of the ConfigureListFields function:
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('email')
->add('firstName')
->add('lastName')
->add('quizz', null, array('label' => 'Quiz Passd : Result'))
->add('_action', 'actions', array(
'actions' => array(
'inscription' => array('template' => 'ATSAdminBundle:CRUD:list__action_inscription.html.twig'),
'edit' => array(),
)
))
;
}
And here how I get my Quiz object:
public function __toString()
{
return $this->getResult() ;
}
But I want that the Quiz Field become clickable not displaying like a simple String.

I think by default the list view will not link one-to-many objects.
You can do so by creating a custom template (just like you did with actions) where you can loop through the quizes and link them e.g.:
{% block field %}
<div>
{% foreach object.quizzes as quizz %}
....
{% foreach %}
</div>
{% endblock %}
See https://sonata-project.org/bundles/doctrine-orm-admin/master/doc/reference/list_field_definition.html#custom-template
If a quizz is a single related object you just need another admin class for Quizz and allow the show or the edit rule. By default Sonata will link to the edit rule. So if you don't have the role for editing Quizz nothing will be linked. Maybe that is your main problem.
If that is your case try this piece of code to verfiy it:
->add('quizz', null, array('label' => 'Quiz Passd : Result', 'route' => 'show'))
Last but not least it is more common to link to show routes inside the show view of the parent object. You can then add your Quizz(es) inside the tab menu:
protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
$menu->addChild($this->trans('Quizzes'), array(
'uri' => $admin->generateUrl('sonata.admin.quizz.list', array('id' => $id)),
));
}
http://sonataadminbundle.readthedocs.org/en/latest/reference/advanced.html#dropdowns-in-tab-menu
https://github.com/sonata-project/SonataAdminBundle/issues/2883

Related

Using a Pagerfanta/ non ArrayAccess list in a bulk form

I'm adding checkboxes for bulk actions to a CRUD list, using the solution provided here.
However, my results are paged with Pagerfanta, so it seems I need to use a DataMapper in my form.
I have tried various solutions, but cannot get the selected fields to be available in my form data:
class ModelEntitySelectionType extends AbstractType implements DataMapperInterface
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('model_id', EntityType::class, [
'required' => false,
'class' => ModelFile::class,
'choice_label' => 'id',
'property_path' => '[id]', # in square brackets!
'multiple' => true,
'expanded' => true
])
->add('action', ChoiceType::class, [
'choices' => [
'Delete' => 'delete'
]
])
->add('submit', SubmitType::class, [
'label' => 'Process'
])
->setDataMapper($this)
;
}
public function setDefaultOptions(ExceptionInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
'csrf_protection' => false
));
}
public function mapDataToForms($data, $forms)
{
// there is no data yet, so nothing to prepopulate
if (null === $data) {
return;
}
$formData = [];
/** #var FormInterface[] $forms */
$forms = iterator_to_array($forms);
$forms['model_id']->setData($formData);
}
public function mapFormsToData($forms, &$data)
{
//$forms = iterator_to_array($forms);
$data = [
'model_id' => iterator_to_array($data)
];
}
The missing piece is when I investigate mapFormsToData with a debugger:
$forms is a RecursiveIteratorIterator
$data is a PagerFanta object
I understand how I have to "loop" through the PagerFanta object, because it doesn't have ArrayAccess, but where is the data of which checkboxes have actually been ticked? Also, my other form fields (action) are not accessible here
I think your approach is problematic. The Form component is meant to modify the object passed to it, which is - as far as I can tell - not what you want. You don't want to modify a Pagerfanta object, you want to select entities for bulk actions.
So to solve your problem, the very very raw things that have to happen: A <form> must be displayed on the page with a checkbox for every entry that's a candidate for the bulk action, with some button(s) to trigger the bulk action(s).
Your form - besides the entry for checkboxes - is alright I guess and not really your problem, as far as I can tell. You're not even interested in editing the Pagerfanta object (I hope) and just want the selection. To do that, we provide the collection of objects that are queued to be displayed on the page to the form via an option, and then use that option to build the field (read: pass the collection to the EntityType field).
Adding the collection to the form (call) as an option:
Somewhere in your controller, you should have something like:
$form = $this->createForm(ModelEntitySelectionType::class, $pagerfanta);
Change this to:
$form = $this->createForm(ModelEntitySelectionType::class, [], [
'model_choices' => $pagerfanta->getCurrentPageResults(),
]);
the method getCurrentPageResults return the collection of entities for the current page (obviously). The empty array [] as the second parameter is ultimately the object/array you're trying to edit/create. I've chosen an array here, but you can also make it a new action class (like a DTO) e.g. ModelBulkAction with properties: model and action:
class ModelBulkAction {
public $model;
public $action;
}
Note these kinds of objects only make sense if used in more than one place - then the call would be:
$form = $this->createForm(ModelEntitySelectionType::class, new ModelBulkAction(), [
'model_choices' => $pagerfanta->getCurrentPageResults(),
]);
Pass the choices to the sub form:
The Form component will complain, if you provide an option to a form, which doesn't expect that option. That's the purpose of AbstractType::configureOptions(OptionsResolver $resolver). (side note: I don't know, what your setDefaultOptions is supposed to achieve, tbh, with an ExceptionInterface nonetheless. No clue, really).
public function configureOptions(OptionsResolver $resolver) {
$resolver->setRequired([
'model_choices', // adds model_choices as a REQUIRED option!
]);
$resolver->setDefaults([
// change null to ModelBulkAction::class, if applicable
'data_class' => null,
]);
}
and finally actually passing the collection to the entity type sub form:
// in ModelEntitySelectionType::buildForm($builder, $options)
$builder->add('model', EntityType::class, [
'required' => false,
'class' => ModelFile::class,
'choice_label' => 'id',
'choices' => $options['model_choices'], // set the choices explicitly
'multiple' => true,
'expanded' => true,
])
// ...
;
Also, your data mapping is not needed any more and should be removed.
Adding the form widgets to the output
This is pretty much similar to the Stack Overflow question and answer you linked. However, the keys in the form are different, because my approach is slightly different:
{{ form_start(form) }}
{% for entity in pagerfanta %}
{# stuff before the checkbox #}
{{ form_widget(form.model[entity.id]) }}
{# stuff after the checkbox #}
{% endfor %}
{# place the selection of action somewhere! and possibly the "submit" button #}
{{ form_widget(form.action) }}
{{ form_end(form) }}
(note: this will probably show the id of the entry next to the checkbox, since that's your choice_label, I believe this can be removed by: {{ form_widget(form.model[index], {label:false}) }} or alternatively by setting the choice_label to false instead of 'id').
Getting your bulk entities
After $form->handleRequest($request); you can check for submission and the form values:
if($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// $data['model'] contains an array of entities, that were selected
// $data['action'] contains the selection of the action field
// do the bulk action ...
}
If you implemented the ModelBulkAction approach, $data is an object of that kind and has $data->model as well as $data->action for you to use (or pass on to a repository).
More stuff
Obviously the model_choices option can be named almost any way you like (but should not clash with existing options the AbstractType may have).
To make an entity selectable (besides the checkbox), you can for example use <label for="{{ form.model[index].vars.id }}"><!-- something to click --></label> as a non-javascript approach (may add styling). With js it's pretty much irrelevant because you probably just need to select the first checkbox in the row.
Alternatives
Alternative to providing the collection of objects to the form, you could theoretically also provide a list of ids and use the ChoiceType instead of the EntityType. There is nothing to be gained from this though.

Value goes in the database, but doesn't show up in the view - Symfony3

I have a project related to restaurants.
I have an entity restaurant with several fields, and a foreign key related to another entity called people
Once I created the restaurant page with its form, and can view the restaurant in its view (show.html.twig), I should be able to click on a link that lets me add a value for how many people can eat there
This should open a new page, with a little form where I can add this value. Once submitted, I should be redirected to the restaurant page (show.html.twig) and then see the value which I just entered.
The FormType I created to add the number of people
class PeopleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('value', NumberType::class, array(
'label' => 'How many people',
'required' => false,
))
->add('save', SubmitType::class, array(
'label' => 'submit'
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => People::class,
));
}
}
and I created a specific controller for that
class PeopleController extends Controller
{
public function PeopleAction(Request $request, Restaurant $restaurant)
{
$people = new People();
$form = $this->createForm(PeopleType::class, $people);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($people);
$em->flush();
return $this->redirectToRoute('admin_restaurant_show', array('id' => $restaurant->getId()));
}
return $this->render('admin/restaurant/people.html.twig', array(
'restaurant' => $restaurant,
'form' => $form->createView()
));
}
}
and the route I created
admin_restaurant_people:
path: /{id}/people
defaults: { _controller: "AdminBundle:Retrocession:people" }
in my restaurant view(show.html.twig) where I already added a restaurant with a form. I have the link to the route going to my PeopleType form view
Add people
So once on this page I can add my value, and then redirect to the restaurant page show.html.twig when clicking on submit
And then to be able to display the value in the restaurant view, I added a twig field to be able to show it
<p>People</p>
{% for value in restaurant.people.values %}
<p>{{ value }}</p>
{% endfor %}
But then the value that was entered in the form, doesn'tshow up in the view. It is right in the database, but the view itself doesn't let me see it even with the twig.
here is my database with the People entity
I think I missed something somewhere. Can you help me find the problem?
Thank you
So in fact, you have an array. To resolve your issue, you can try to display your data in for loop
{% for value in restaurant.people.values %}
{{ value }}
{% endfor %}
I think you are expecting a single value. If that's the case, you should check your entities relations to figure out why you are getting an array.
To display only the first value, here is a ugly workaround with slice
{% for value in restaurant.people.values[:1] %}
{{ value }}
{% endfor %}
If you want sum all the values, you try that :
{% set sum = 0 %}
{% for value in restaurant.people.values[:1] %}
{% set sum = sum + value %}
{% endfor %}
At least, the above solutions will suppress your error.

Add a choice list(dropdown) from 2 different entities in a single table in Symfony 2

I am looking for a method on how to possibly create a single dropdown for my form in symfony 2 that contains the value of the field 'abbr1' and 'abbr2' from a single record in table Params.
Lets say i have a single record in my table Params.
id: 1
title: sample
abbr1: qw12
abbr2: er34
Now i want to pick abbr1 and abbr2 as the value of a single dropdown. I have created a form but i dont know how to make both of them a choice. I can only pick them as a property one at a time. Here is my code:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'desiredAbbr',
'entity',
array(
'class' => 'FooBarBundle:Params',
'property' => 'abbr1',
//'property' => 'abbr2',
)
)
->add('save','submit',array('label'=>'Submit'))
;
}
Any Suggestions are much appreciated. Thanks a lot.
Update:
The expected dropdown value would look like this in html format:
{% for par in parameters %}
<select>
<option>{{param.abbr1}}</option> {# qw12 #}
<option>{{param.abbr2}}</option> {# er34 #}
</select>
{% endfor %}
ok, I missed that you want them as value, not as label. Then you should change you form like this
$choices = $options['abbrChoices'];
$builder->add('desiredAbbr', ChoiceType::class, array(
'choices' => $choices,
));
// in configureOptions method
$resolver->setDefaults(array(
'abbrChoices' => array(),
));
In controller where you create the form
$params = $this->getDoctrine()->getRepository('AppBundle:Params')->findAll();
$choices = array();
foreach ($params as $p) {
// key will be used as option value, value as option title
$choices[$p->getAbbr1()] = $p->getAbbr1();
$choices[$p->getAbbr2()] = $p->getAbbr2();
}
$form = $this->createForm(myform::class, array(), array('abbrChoices' => $choices));
BUT. How are you going to use this choice?

Symfony3 Render multiple time same form

I would like to render the same form multiple times to handle the same action for two different tabs.
The problem is that when I try, only the form of the first tab is shown, event if I change the id and name of the form.
I found out it's the expected behavior of symfony, but I still need it to work.
I found that it may works with a collection but don't get how it would work.
twig:
{{ form(contactForm, {'attr': {'id': 'contactFormId' ~ Client.Id}, 'name': "contactFormName" ~ Client.Id})}}
Form:
$this->contactForm = $this->createFormBuilder($contact, array('allow_extra_fields' =>true))
->add('Nom', TextType::class, array('mapped'=>false))
->add('Prenom', TextType::class, array('mapped'=>false))
->add('Telephone', TextType::class, array(
'label' => 'Téléphone'))
->add('Email', TextType::class)
->add('Ajouter', SubmitType::class)
->getForm();
It is an older question, but I just came across it facing a similar situation. I wanted to have multiple versions of one form object in a list view. For me the solution was to move the createView() call on the form object to the view instead of calling it in the controller. This is kind of a dirty solution regarding separation of concerns, but I thought to post it so it may help others anyway.
My controller action looks like this:
/**
* #Route("", name="cart_show")
* #Method("GET")
*/
public function showAction(Request $request)
{
/** #var CartInterface $cart */
$cart = $this->get('rodacker.cart');
$deleteForm = $this->createDeleteForm();
return $this->render(
'AppBundle:Cart:show.html.twig',
['cart' => $cart, 'deleteForm' => $deleteForm]
);
// ...
private function createDeleteForm()
{
return $this->createForm(
OrderItemDeleteType::class,
null,
[
'action' => $this->generateUrl('cart_remove_item'),
'method' => 'DELETE',
]
);
}
}
and in the view I set the form variable by calling the createView function on the form variable (deleteForm) passed from the controller:
{% for item in items %}
{% set form = deleteForm.createView %}
{{ form_start(form) }}
{{ form_widget(form.item, {'value': item.image.filename}) }}
<button type="submit" class="btn btn-xs btn-danger" title="Artikel entfernen">
<i class="fa fa-trash-o"></i> entfernen
</button>
{{ form_end(form) }}
{% endfor %}
Once you render a Symfony form, the same form will not render again.
I would suggest creating a form class and calling Controller::createForm() multiple times to create the desired amount of Form instances; you can call isSubmitted etc. on all forms independently.
http://symfony.com/doc/current/book/forms.html#creating-form-classes

Form Type Default Value for empty value in rendered form

I found some weird behavior with a rendered controller with displays a edit form for my entity.
But first things first:
I'm rendering a template with displays a entity. If the logged in user is the same user as the owner of that entity i also render another controller hidden with contains the edit form for this entity. The User can access this via a button which fires a jQuery toggle.
The entity has 2 textfields which can be empty, description and situation.
So if one of the two or both are empty the edit form will display in the textfield (null) by default. I do not want that! How can i fix this so that the textfields are empty like the value of the field (so that my placeholder will be shown).
Here's an image to visualize this:
But further: This entity (Poi) belongs to another Entity (Turn), so 1 Turn -> many Pois. You can navigate through the pois in my website.
But if the owner navigtes through them (keep in mind, the edit form will be rendered, but not displayed until the button was klicked) all description and situation fields now display (null), even he did not saved the edit. It just happen by itself.
Here an image which shows it
Why does this happen? What can i do against it? Is there maybe something like an empty value option in the form type?
I searched for a solution, but i couldn't find anything that is nearly simliar with my situation.
The form build from my Form Type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text', array(
'required' => false
))
->add('situation', 'textarea', array(
'required' => false
))
->add('description', 'textarea', array(
'required' => false
))
->add('isPrivateText', 'checkbox', array(
'required' => false
))
->add('isPrivateImage', 'checkbox', array(
'required' => false
))
;
}
The relevant part of my edit.html.twig
<p class="edit_form"><span class="edit_left">{{ form_label(edit_form.situation, 'Situation') }} </span>
<span class="edit_right">{{ form_widget(edit_form.situation, { attr: {'placeholder': 'Törn Situation'} }) }}</span></p>
<p class="edit_form"><span class="edit_left">{{ form_label(edit_form.description, 'Beschreibung') }} </span>
<span class="edit_right">{{ form_widget(edit_form.description, { attr: {'placeholder': 'Törn Beschreibung'} }) }}</span></p>
Where my showPoi.html.twig renderes the form controller:
<div class="col-md-6 col-sm-6 toggle_edit" style="display: none;">
<div>
{% render controller('MysailinglogMysailinglogBundle:Poi:edit', { id: poi[0].id , poi: poi}) %}
<!-- Don't worry about the 2 divs, i just shortened up the code -->
</div>
</div>
After lots of more research i found a solution that is working fine
I'm adding a listener to my formType which leads to the following function:
function onPreSetData(FormEvent $event) {
$data = $event->getData();
if($data->getDescription() == "(null)"){
$data->setDescription('');
}
if($data->getSituation() == "(null)"){
$data->setSituation('');
}
return $event->setData($data);
}
It just takes the data from the event which will build the form and is nothing more then the Poi Entity. There i simply check if the value is (null) and if it is i set it to a empty string.
Registering the listener is also easy, it`s done with this simple line of code:
$builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData'));
This must be done with a instance of the FormBuilder, the "onPreSetData" must be the same name as the function above which will be triggered by the event.
It's important to mention that the Event must be the PRE_SET_DATA event in this situation because i wanted to manipulate the data before they're written into the form!
You can set up an empty data attribute in the Form type:
Symfony documentation
$builder->add('description', 'textarea', array(
'required' => false,
'empty_value' => 'Choose your gender',
'empty_data' => null
));