I currently getting an error if I try to render a conditionally added form element in twig. The form element was added (or not) through the form event listener mechanism and should only add the form element if a specific form option is set.
Error
Argument 1 passed to Symfony\Component\Form\FormRenderer::searchAndRenderBlock() must be an instance of Symfony\Component\Form\FormView, null given
Form
<?php
namespace Vendor\ProjectBundle\Form\Type;
// [...]
abstract class AbstractContextualInfoFormType extends AbstractFormType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('user', new UserFormType($this->getTranslator(), $this->getDoctrine()), array('error_bubbling' => true, 'validation_groups' => 'ValidationGroup'));
$creditcardForm = new CreditcardFormType($this->getTranslator(), $this->getDoctrine());
$creditcardForm->setProcess($options['process']);
$creditcardForm->setProvider($options['provider']);
if (array_key_exists('cvc', $options)) {
$creditcardForm->setRequireCvc($options['cvc']);
}
if (array_key_exists('types', $options)) {
$creditcardForm->setAllowedCreditcardTypes($options['types']);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($options) {
if (!array_key_exists('disable_creditcard', $options) OR (array_key_exists('disable_creditcard', $options) AND $options['disable_creditcard'] === true)) {
$creditcardForm = new CreditcardFormType($this->getTranslator(), $this->getDoctrine());
$creditcardForm->setProcess($options['process']);
$creditcardForm->setProvider($options['provider']);
if (array_key_exists('cvc', $options)) {
$creditcardForm->setRequireCvc($options['cvc']);
}
if (array_key_exists('types', $options)) {
$creditcardForm->setAllowedCreditcardTypes($options['types']);
}
$form = $event->getForm();
$form->add('creditcard', $creditcardForm, array('error_bubbling' => true));
}
}
);
}
}
// [...]
As you can see i try to add the credit card form only if the option disable_creditcard is not set. This all works fine until the moment I try to browse the page where I implemented the form:
Template
{% if not disable_creditcard %}
<div id="detail_creditcard" class="creditcard">
<legend>{{ 'creditcard.content.title'|trans }}</legend>
<div class="alert alert-info">
<i class="icon-info-sign"></i>
Bla bla bla text
</div>
**{{ form_row(form_data.creditcard.owner) }}**
{{ form_row(form_data.creditcard.number) }}
{{ form_row(form_data.creditcard.type) }}
{{ form_row(form_data.creditcard.validity) }}
{{ form_rest(form_data.creditcard) }}
</div>
{% endif %}
I also tried it with a surrounded conditional-if, but that doesn't work at all... I think twig needs the "not defined" creditcard form element here but cannot find it.
What is the right way for doing this? I would appreciate any help from you. :-)
Thanks!
try this:
{% if form_data.creditcard is defined %}
... your conditional code here
{% endif %}
Related
Im learning Symfony and I'm creating a CRUD app for practicing.
I want to implement a search function in the page where I list my db items. I was wondering what is the correct way to achieve this.
Right now, I have created a searchType and searchController with the next code:
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class searchController extends Controller
{
public function searchAction(){
$formulario = $this->createForm('AppBundle\Form\SearchType');
return $this->render('searchBar.html.twig', ['form' => $formulario->createView()]);
}
}
class SearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('key', ChoiceType::class,
['choices' => [
'Elegir Campo...' => 0,
'Modelo' => 1,
'Marca' => 2,
'Año' => 3,
'Propietario' => 4
]
])
->add('term', TextType::class)
->add('buscar', SubmitType::class)
;
}
}
I have another controller called itemController, where i have the list, add, modify and delete actions. With the twig render() function, I'm rendering the searchBar in the items list page. Which is the correct way to get the values from the 'key' and the 'term' elements and use them to make queries against the db?
I have tried to achieve this without the searchController/searchType and I used a simple <form> in the template and got the key and term values with $request->get() method in the listAction. After I created a switch-case statement to execute queries according to the key value. I could achieve what i wanted like this, but I want to be able to do this the correct way.
Can someone help me/give me some hints on how to continue from this?
Thanks.
Update:
Items Controller:
/**
*#Route("/items", name="items")
*/
public function listAction(Request $request){
$em = $this->getDoctrine()->getManager();
$items = $em->getRepository('AppBundle:Item')->findAll();
return $this->render('items.html.twig', ['items' => $items]);
}
My items.html.twig:
{% extends base.html.twig %}
{% block body %}
{{ render(controller('AppBundle:search:search')) }}
...
{% endblock %}
My searchBar.html.twig:
{{ form_start(form, {'attr': {'class': 'form-inline float-left my-2 my-lg-0'}}) }}
{{ form_widget(form.key) }}
{{ form_widget(form.term, {'attr': {'class': 'ml-1'}}) }}
{{ form_widget(form.buscar, {'attr': {'class': 'btn btn-outline-success ml-1'}}) }}
{{ form_end(form) }}
What i tried with routing and works with the searchController:
/**
* #Route("/search", name="search")
*/
public function searchAction(Request $request){
$em = $this->getDoctrine()->getManager();
$formulario = $this->createForm('AppBundle\Form\SearchType');
$formulario->handleRequest($request);
if($formulario->isSubmitted() && $formulario->isValid()){
$data = $formulario->getData();
$key = $data["key"];
$term = $data["term"];
$items = $em->getRepository('Item::class')->findByTerm($key, $term);
return $this->render('items.html.twig', ['items' => $items]);
}
return $this->render('searchBar.html.twig', ['form' => $formulario->createView()]);
}
If i go to /search and search for an item, it redirects me to my items page with the item i searched. But, If i use the search bar in my items page that i rendered using {{ render(controller('AppBundle:search:search')) }}, it doesn't work.
You are not very far from reaching your goal.
On your Controller, you can update your code to process the incoming request:
public function searchAction(Request $request){
$formulario = $this->createForm('AppBundle\Form\SearchType');
$formulario->handleRequest($request);
if ($formulario->isSubmitted() && $formulario->isValid()) {
$data = $formulario->getData();
// ... perform some action, such as saving the data to the database or search
}
return $this->render('searchBar.html.twig', ['form' => $formulario->createView()]);
}
You can find here more information about Processing Forms
To search into your database, you can process your repositories corresponding to the data. For more information about that, you can go here on Symfony Doctrine documentation
To go further, there is a bundle allowing simplified management of search forms: Lexik Form Filter Bundle
Using Symfony 4 to build a support ticket form:
Created route and functions in page controller
/**
* #Route("/support/ticket")
*/
public function ticket(){
return $this->render('support/ticket/ticket.html.twig');
}
public function new(Request $request)
{
// creates a Ticket and gives it some dummy data for this example
$ticket = new Ticket();
$form = $this->createFormBuilder($ticket)
->add('category', ChoiceType::class, array(
'choices' => array(
'ROMAC eHR' => 1,
'ROMAC Website' => 2,
'ROMAC Guide' => 3,
)
))
->add('comment', TextareaType::class)
->add('save', SubmitType::class, array('label' => 'Submit Ticket'))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// $form->getData() holds the submitted values
// but, the original `$task` variable has also been updated
$ticket = $form->getData();
// ... perform some action, such as saving the task to the database
// for example, if Ticket is a Doctrine entity, save it!
// $entityManager = $this->getDoctrine()->getManager();
// $entityManager->persist($task);
// $entityManager->flush();
return $this->redirectToRoute('ticket_success');
}
return $this->render('support/ticket/ticket.html.twig', array(
'form' => $form->createView(),
));
}
And then render the form in the twig template:
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.category) }}
{{ form_row(form.comment) }}
{{ form_end(form) }}
When I load the page I get a Symfony error stating that "Variable form does not exist".
I have followed the documentation https://symfony.com/doc/current/forms.html. Where/how can I find the issue?
I assume you get this error while accessing "/support/ticket"
You have "form" variable missing in this function
public function ticket(){
return $this->render('support/ticket/ticket.html.twig');
}
I will suggest to wrap your code in twig template in a "if" block
{% if form is defined %}
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.category) }}
{{ form_row(form.comment) }}
{{ form_end(form) }}
{% endif %}
Or you need to adjust your controller functions accordingly
In Symfony I have a twig page that displays a form with text fields and check boxes. The form contains data for a question and four possible answers. The user can edit the data and select one answer that is correct.
At the moment I have all the text fields where the user can change the data and four check boxes. Instead of the check boxes I need radio buttons(this is to allow the user to select only one choice). Also I need the check boxes to be on the right hand side of text fields for each possible answer. How can I do this in Symfony. Would much appreciate some help.Thanks.
Using Collection of Forms to build the entire Form
Answer Form:
class AnswerFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('answer');
$builder ->add('isCorrect', null , array('label' => false,));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => 'QuizBundle\Entity\Answer'));
}
public function getName()
{
return 'quiz_bundle_answer_form_type';
}
}
Question Form:
class QuestionFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('image');
$builder->add('question');
$builder->add('answers', CollectionType::class, array('entry_type' => AnswerFormType::class));
$builder->add('Submit', SubmitType::class, array('label' => 'Submit'));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => 'QuizBundle\Entity\Question'));
}
public function getName()
{
return 'quiz_bundle_question_form_type';
}
}
This is my Twig:
{% extends 'base.html.twig' %}
{% block body %}
Edit record page
{{ form_start(form) }}
{{ form_row(form.image) }}
{{ form_row(form.question) }}
{% for answers in form.answers %}
<li>{{ form_row(answers.answer) }}</li>
{% endfor %}
{{ form_end(form) }}
{% endblock %}
You can not mix a radio input with text inputs to let the user edit them.
It is not possible with HTML.
No other choice, you have to :
Recreate the radio behavior with Javascript on the client side
Write some CSS to make your checkboxes looks like radio boxes.
Attach a form constraint which validates only one checkbox is selected after submit.
To put the text inputs at the right side, you have to customize the form widgets. Take a look at the documentation : http://symfony.com/doc/current/cookbook/form/form_customization.html
I understand what you are trying to do because of your previous question. For layout of the form, you can do something like this to make it look better:
{{ form_start(form) }}
{{ form_label(form.image) }}{{ form_widget(form.image) }}
{{ form_label(form.question) }}{{ form_widget(form.question) }}
{% for ans in form.answers %}
<li>{{ form_label(ans.answer) }}{{ form_widget(ans.answer) }}
{{ form_label(ans.isCorrect) }}{{ form_widget(ans.isCorrect) </li>
{{ form_end(form) }}
However, I don't think this is the solution for you. Like Alsatian mentioned, you need to use Javascript to check for "onchange" events for the radio buttons. You are creating a form class, which in my opinion doesn't always work for every design. You might try just a "createFormBuilder" instead and customize exactly as you need.
public function showQuestionFormAction($ansID, Request $request){
// Get Answers...
$em = $this->getDoctrine()->getManager();
$qb->select('a')
->from('AppBundle:Answer', 'a')
->where('a.answer_id = :ansID')
->setParameter('ansID', $ansID);
$ans = $qb->getQuery()->setMaxResults(1)->getOneOrNullResult();
$form = $this->createFormBuilder()
->add('image') // Not sure what type this is???
->add('question', EntityType::class, array(
'class' => 'AppBundle:Question',
'label' => 'Question:',
'choice_label' => 'question', // Label shown.
'choice_value' => 'question_id', // What data you want returned.
'attr' => array('class' => 'question_id'), // For css styling only, you may not need this.
'data' => $ques->getText(), // This is a method (getter) to get question text.
))
->add('ans1', RadioType::class, array(
'label' => '$ans->getAnswer1()',
'required' => false,
'attr' => array(
'onchange' => "check_answer('ans1')", // Javascript function.
'checked' => true,
),
))
->add('ans2', RadioType::class, array(
'label' => '$ans->getAnswer2()',
'required' => false,
'attr' => array(
'onchange' => "check_answer('ans2')", // Javascript function.
'checked' => false,
),
))
...
Then you can use a simple javascript function like so. I made this quickly, so there may be a better/simpler way to design this.
// script/check_answer.js
function check_answer(id){
var ans1 = document.getElementById("form_ans1");
var ans2 = document.getElementById("form_ans2");
var ans3 = document.getElementById("form_ans3");
var ans4 = document.getElementById("form_ans4");
// Check what is changed.
if(id == 'ans1'){
ans2.checked = false;
ans3.checked = false;
ans4.checked = false;
}
if(id == 'ans2'){
ans1.checked = false;
ans3.checked = false;
ans4.checked = false;
}
...
}
Hope that helps.
I have an entity Playlist that is related to another call Items.
I want to deploy on a twig template id of each of the items.
class Playlist
{
private $items;
public function __construct()
{
$this->items = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addItem(\Publicartel\AppBundle\Entity\PlaylistContent $content)
{
$content->setPlaylist($this);
$this->duration += $content->getDuration();
$this->items->add($content);
return $this;
}
public function removeItem(\Publicartel\AppBundle\Entity\PlaylistContent $content)
{
$this->items->removeElement($content);
$this->duration -= $content->getDuration();
}
public function getItems()
{
return $this->items;
}
}
I want to deploy in the Playlist form the id of the item.
I've tried like so:
{% for content in edit_form.items %}
{{ content.getId() }}
{{ content.id() }}
{% endfor %}
But I get the following error:
Method "getId" for object "Symfony\Component\Form\FormView" does not
exist Method "id" for object "Symfony\Component\Form\FormView" does
not exist
I've added the id attribute to my FormType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id')
->add('position')
->add('duration')
->add('playlist')
->add('content')
;
}
But I get the following error:
An exception has been thrown during the rendering of a template
("Catchable Fatal Error: Object of class
Symfony\Component\Form\FormView could not be converted to string")
I've also tried:
// Controller
$entity = $em->getRepository('PublicartelAppBundle:Playlist')->find($id);
return $this->render('PublicartelAppBundle:Playlist:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
// Twig
{{ entity.items.id }}
But that too throws an error:
Method "id" for object "Doctrine\ORM\PersistentCollection" does not exist
Thanks!
Solution 1
Last part of your solution is the good one
$entity = $em->getRepository('PublicartelAppBundle:Playlist')->find($id);
return $this->render('PublicartelAppBundle:Playlist:edit.html.twig', array(
'entity' => $entity,
[...]
));
What isn't good is that snippet of code
{{ entity.items.id }}
You have to cycle over all items before print id out (from your error, is pretty clear)
{% for item in entity.items %}
{{ item.id }}
{% endfor %}
Solution 2 (Didn't tested)
You can, of course, access also data from underlying object of a form, without pass it from the controller. So you can change your snippet of code from
from
{% for content in edit_form.items %}
{{ content.getId() }}
{{ content.id() }}
{% endfor %}
to
{% for content in edit_form.vars.data %}
{{ content.id() }}
{% endfor %}
Your error was that you were trying to access FormView (passed from controller) and not entity "binded" to the form
I am trying to post data through Symfony form's button but it does not validate form.
Here is my controller file:
public function PurchaseProductAction(Request $request)
{
$defaultData = array('message' => 'Type your message here');
$form = $this->createFormBuilder($defaultData)
->setMethod('POST')
->add('CompanyName', 'text', array(
'label'=>false
))
->add('Address1', 'text', array(
'label'=>false
))
->add('Continue_to_Step_2', 'submit')
->getForm();
$form->handleRequest($request);
if ($form->isValid())
{
// It does not come here
$data = $form->getData();
$value = $data['CompanyName'];
echo $value;
}
}
Its my twig file:
{% block content %}
Company Name{{ form_row(form.CompanyName) }}
Address Line 1{{ form_row(form.Address1) }}
{{form_widget(form.Continue_to_Step_2)}}
{% endblock %}
Kindly guide me what I am doing wrong due to which my method does not call?
As explained in the Rendering a Form in a Template part of the documentation, you've to include the {{ form_start(form) }} and the {{ form_end(form) }} twig form helpers.
This will generate the appropriate <form> tags according to your form definition.
Also, keep in mind that Support for submit buttons was added in Symfony 2.3. Before that, you had to add buttons to the form's HTML manually.
Update,
form_end should be called with render_rest option set to false if you don't want it to show unrendered fields,
{# don't render unrendered fields #}
{{ form_end(form, {'render_rest': false}) }}