I've implemented Gedmo nested trees in my Symfony2 project and I'm trying to build a parent select. I read the doc https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/tree.md
but it's very short note about this kind of issues.
I can't get an array of nodes with a identation based on node levels.
$repo = $em->getRepository('SymdruMenuBundle:MenuLink');
$options = array(
'decorate' => true,
'nodeDecorator' => function($node) {
return str_repeat(' ', $node['lvl']).'-'.$node['title'].'</a>';
},
'html' => false,
);
$htmlTree = $repo->childrenHierarchy(
null, /* starting from root nodes */
false, /* true: load all children, false: only direct */
$options
);
var_dump($htmlTree);
$form->add('parent', 'choice', array('choices' => $htmlTree));
It gives me a string instead of an array.
I can do it like this
$em = $this->getDoctrine()->getManager();
$links = $em->getRepository('SymdruMenuBundle:MenuLink')->findAll();
$choices = array();
foreach ($links as $link) {
$choices[$link->getId()] = str_repeat('--', $link->getLvl()).$link->getTitle();
}
but is this the best way?
The second issue here is how to get a parent object when I save the children to the database?
Related
I have a form that contains 3 fields (date, typeEvent, seller) where Seller is a choiceType that depends on date and typeEvent, and to do that i followed the symfony documentation for dynamics forms.
but the exemple in the doc its about a field that depends on only one other field.
what i did so far :
$formModifier = function (FormInterface $form,DateTime $date = null, TypeEvent $type = null) {
if (($date === null) || ($type === null)) {$sellers = [];return;}
$repo = $this->entityManager->getRepository(User::class);
$start = $date->format("Y-m-d H:i:s");
$end = new DateTime($date->format("Y-m-d H:i:s"));
$end = date_add($end,date_interval_create_from_date_string("60 minutes"))->format('Y-m-d H:i:s');
$organisation = $this->security->getUser()->getOrganisation();
$sellers = $repo->findSellers($organisation,$start,$end);
$form->add('seller', EntityType::class, [
'class' => User::class,
'placeholder' => '',
'choices' => $sellers,
'choice_label' => 'pseudo',
'attr' => ['class'=>'seller-select'],
'required'=>false,
'expanded' =>false,
]);
};
$builder->get('start')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$start = $event->getForm()->getData();
$type = $event->getForm()->getParent()->getData()->getTypeEvent();
$formModifier($event->getForm()->getParent(), $start, $type);
}
);
$builder->get('typeEvent')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$type = $event->getForm()->getData();
$start = $event->getForm()->getParent()->getData()->getStart();
$formModifier($event->getForm()->getParent(), $start, $type);
}
);
the problem here is that, for exemple when i try to add a listener to 'start' field inside of it, i don't have access to the other fields, the typeEvent field specifically, i tried $event->getForm()->getParent()->getData()->getTypeEvent() but it returns null, and that's $event->getForm()
dumped.
As you can see the $event->getForm()->getParent()->getData() it's like a new Event() with all attribute on null.
So my question is: There is any way to get the typeEvent there ? or should i proceed differently?
Thank you.
I am not completely sure if this is what you want, but you should take a look at this answer:
https://stackoverflow.com/a/25890150/17089665
$form->all(); <- Will get you all fields
$child->getName(); <- Will get you the name of every child, if you iterate the $form variable.
Overview
I have two entities;
Element
ElementAnswer
Element has a self-referencing relation and ElementAnswer has a One-To-Many relation with Element (One Element can have many ElementAnswers).
I'm using Symfony2 with embedded collection and it's is working (together with javasript). I can add multiple 'child' elements to the 'parent' element. Also i can add multiple answers to the child element. But.. When i add a child element and add answers to the child element only the child element gets persisted. The answers (which is an embedded collection to the child elements) don't get persisted. AND when i edit the whole collection the answers are persisted.
To be more precise i made some screenshots AND i debugged the post data. Which is exactly the same.
ElementType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('question')
//->add('answers')
->add('answers', 'collection', array(
'entry_type' => new AnswerType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'empty_data' => null,
))
->add('conditions', 'collection', array(
'entry_type' => new ConditionalType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'empty_data' => null,
))
//->add('children', )
->add('children', 'collection', array(
'entry_type' => new ElementChildType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'empty_data' => null,
))
;
}
ElementChildType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('answers', 'collection', array(
'entry_type' => new AnswerType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'empty_data' => null,
'block_name' => 'child_element_answer',
'prototype_name' => '__child_element_answer__',
))
;
}
This is how the form looks like before i submit it:
(Child element 1 and 2 are already persisted. Child element 3 is a newly added entity in the form
This is the post data:
element[name]:parent-element-name
element[question]: blabla
element[children][0][name]:child element 1
element[children][0][answers][0][name]:child answer 1-1
element[children][1][name]:child element 2
element[children][1][answers][0][name]:child answer 2-1
element[children][2][name]:child element 3
element[children][2][answers][0][name]:child answer 3-1
element[_token]:xxxxxxxxxxxxxxxxxxxxxxxxxxxx
This is how the form looks like after submit:
(As you can see: there is no child answer in child element 3)
And when i add a child answer now (after submitting) the child answer DOES get persisted. The same controller is being called and the same entities, so i am stuck. Please help. Thank you!
EDIT:
I finally edited my controller the following way. Probably not the best practice, but it's working so i thought i should let you know:
/**
* Displays a form to edit an existing Element entity.
*
* #Route("/{id}/edit", name="element_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Element $element, $firstPost = NULL)
{
$em = $this->getDoctrine()->getManager();
$deleteForm = $this->createDeleteForm($element);
$editForm = $this->createForm('AppBundle\Form\ElementType', $element);
$editForm->handleRequest($request);
$breadcrumbs = array();
$breadcrumbs[] = array("name" => "Dashboard", "url" => $this->get("router")->generate("dashboard"));
$breadcrumbs[] = array("name" => "Elementen lijst", "url" => $this->get("router")->generate("element_index"));
$breadcrumbs[] = array("name" => "Element bewerken");
if ($editForm->isSubmitted() && $editForm->isValid()) {
$originalChildren = new ArrayCollection();
$postChildren = new ArrayCollection();
$newChildren = new ArrayCollection();
foreach ($element->getChildren() as $child) {
$originalChildAnswers = new ArrayCollection();
$originalChildren->add($child);
foreach ($child->getAnswers() as $childAnswer) {
$originalChildAnswers->add($childAnswer);
}
}
if (isset($request->request->get('element')['children'])) {
foreach ($request->request->get('element')['children'] as $postChild) {
$postChildren->add($postChild);
}
}
$newElements = [];
$i = 0;
foreach ($originalChildren as $child) {
if (null != $child->getId() && $element->getChildren()->contains($child)) {
$i++;
} else {
$newElements[] = $i;
$i++;
}
}
foreach ($newElements as $newElement) {
$newChildren->set($newElement, $postChildren[$newElement]);
}
foreach ($newChildren as $key => $newchild) {
foreach ($newchild['answers'] as $newChildAnswer) {
$childAnswerKey = $element->getChildren()->getKeys()[$key];
$answerEntity = new Answer();
$answerEntity->setName($newChildAnswer['name']);
$element->getChildren()[$childAnswerKey]->addAnswer($answerEntity);
}
}
$em->persist($element);
$em->flush();
return $this->redirectToRoute('element_edit', array('id' => $element->getId()));
}
return $this->render('element/edit.html.twig', array(
'breadcrumbs' => $breadcrumbs,
'element' => $element,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
How can I display the titles from a many_many relation in a GridField Summary?
I tried it with RelationName.Title but the result was only an empty field
there should be a couple solutions:
defining $summary_fields on the DataObject that is linked:
private static $summary_fields = array(
'YourFieldName',
'AnotherField'
);
or with the GridFieldConfig on the Page/DataObject that defines the relation:
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'FieldName' => 'GridFieldColumnName',
'AnotherFieldName' => 'AnotherGridFieldColumnName',
));
$config being your GridFieldConfig instance used by GridField.
EDIT
for more advanced formatting/control over the data included in the GridField, you can use setFieldFormatting:
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'TeamLink' => 'Edit teams'
));
$config->getComponentByType('GridFieldDataColumns')->setFieldFormatting(array(
'TeamLink' => function($value, $item)
{
// $item here would be a TeamMember instance
// since the GridField displays TeamMembers
$links = 'No teams';
$teamModelAdminClass = 'TeamModelAdmin'; //change to your classname
$teams = $item->Teams(); // get the teams
if ( $teams->count() > 0 )
{
$links = '';
$teamClass = $teams->dataClass;
$teamAdminURL = Config::inst()->get($teamModelAdminClass, 'url_segment');
$teamEditAdminURL = 'admin/'.$teamAdminURL.'/'.$teamClass.'/EditForm/field/'.$teamClass.'/item/';
foreach($teams as $team)
{
$links .= 'Edit '.$team->Title.'<br/>';
}
}
return $links;
}
));
Here setFieldFormatting will output edit links to all teams that a TeamMember is part of in a TeamLink column defined by setDisplayFields (might not be the best example, but hope you get the idea, although not tested).
colymba's answer already said most of it, but in addition you can also specify a method in $summary_fields. This allows you to display image thumbnails in a GridField or as you need it, piece together your own string from the titles of a many_many relation.
class TeamMember extends DataObject {
private static $db = array(
'Title' => 'Text',
'Birthday' => 'Date',
);
private static $has_one = array(
'Photo' => 'Image'
);
private static $has_many = array(
'Teams' => 'Team'
);
private static $summary_fields = array(
'PhotoThumbnail',
'Title',
'Birthday',
'TeamsAsString',
);
public function getPhotoThumbnail() {
// display a thumbnail of the Image from the has_one relation
return $this->Photo() ? $this->Photo()->CroppedImage(50,50) : '';
}
public function getTeamsAsString() {
if (!$this->Teams()) {
return 'not in any Team';
}
return implode(', ', $this->Teams()->Column('Title'));
// or if one field is not enough for you, you can use a foreach loop:
// $teamsArray= array();
// foreach ($this->Teams() as $team) {
// $teamsArray[] = "{$team->ID} {$team->Title}";
// }
// return implode(', ', $teamsArray);
}
}
alternative you can, as colymba pointed out, also use setDisplayFields to use different fields on different grids
using generateUrl in doctrine extensions tree
in action
$repo = $em->getRepository('Entity\Category');
$options = array(
'decorate' => true,
'rootOpen' => '<ul>',
'rootClose' => '</ul>',
'childOpen' => '<li>',
'childClose' => '</li>',
'nodeDecorator' => function($node) {
return ''.$node[$field].'';
}
);
$htmlTree = $repo->childrenHierarchy(
null, /* starting from root nodes */
false, /* true: load all children, false: only direct */
$options
);
error:
FatalErrorException: Error: Using $this when not in object context in
You have to register that as a service and inject the #router
nodeDecorator is a closure, therefor you cannot use this inside. Try this:
//depending in which context you are
$routing = $this->container->get('router');
[...]
'nodeDecorator' => function($node) use ($router) {
return ''.$node[$field].'';
}
How to limit the number of embedded form with the type "sonata_type_collection" ?
$formMapper->add('phones', 'sonata_type_collection',
array(
'required' => true,
'by_reference' => false,
'label' => 'Phones',
),
array(
'edit' => 'inline',
'inline' => 'table'
)
I would like limit to last five phones, I found only this solution for now, limit the display in the template twig "edit_orm_one_to_many", but i don't like that.
I found a solution by rewriting the edit action in the controller,
such in the documentation sonataAdminBundle I created my admin controller class:
class ContactAdminController extends Controller
{
public function editAction($id = null)
{
// the key used to lookup the template
$templateKey = 'edit';
$em = $this->getDoctrine()->getEntityManager();
$id = $this->get('request')->get($this->admin->getIdParameter());
// $object = $this->admin->getObject($id);
// My custom method to reduce the queries number
$object = $em->getRepository('GestionBundle:Contact')->findOneAllJoin($id);
if (!$object)
{
throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
}
if (false === $this->admin->isGranted('EDIT', $object))
{
throw new AccessDeniedException();
}
$this->admin->setSubject($object);
/** #var $form \Symfony\Component\Form\Form */
$form = $this->admin->getForm();
$form->setData($object);
// Trick is here ###############################################
// Method to find the X last phones for this Contact (x = limit)
// And set the data in form
$phones = $em->getRepository('GestionBundle:Phone')->findLastByContact($object, 5);
$form['phones']->setData($phones);
// #############################################################
if ($this->get('request')->getMethod() == 'POST')
{
$form->bindRequest($this->get('request'));
$isFormValid = $form->isValid();
// persist if the form was valid and if in preview mode the preview was approved
if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved()))
{
$this->admin->update($object);
$this->get('session')->setFlash('sonata_flash_success', 'flash_edit_success');
if ($this->isXmlHttpRequest())
{
return $this->renderJson(array(
'result' => 'ok',
'objectId' => $this->admin->getNormalizedIdentifier($object)
));
}
// redirect to edit mode
return $this->redirectTo($object);
}
// show an error message if the form failed validation
if (!$isFormValid)
{
$this->get('session')->setFlash('sonata_flash_error', 'flash_edit_error');
}
elseif ($this->isPreviewRequested())
{
// enable the preview template if the form was valid and preview was requested
$templateKey = 'preview';
}
}
$view = $form->createView();
// set the theme for the current Admin Form
$this->get('twig')->getExtension('form')->renderer->setTheme($view, $this->admin->getFormTheme());
return $this->render($this->admin->getTemplate($templateKey), array(
'action' => 'edit',
'form' => $view,
'object' => $object,
));
}
}