Symfony form inline radios with text fields - php

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.

Related

Correct way to implement a search function in a symfony project?

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

Symfony 4 forms - Variable form does not exist

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

Error: Call to a member function on a non-object using ChoiceType of Symfony component form

I have showUsersAction()-method inside the Defaultcontroller which should render a form where it should be possible to select a user from a list, press a submit-button and then redirects to a route /showItems/{userId} where the items of a user are shown.
I know that it would be possible to do that easy with a link, but I want to make use of ChoiceType:
First I copied an example of ChoiceType from the Symfony documentation with a minimal change:
/**
* #Route("/showUsers", name="showUsers")
*/
public function showUsersAction(){
$users = $this->getDoctrine()->getManager()->getRepository('AppBundle:User')->findAll();
$form = $this->createFormBuilder()
->setMethod('POST')
->add('user', ChoiceType::class, [
'choices' => $users,
'choice_label' => function($user) {
/** #var User $user */
return strtoupper($user->getUsername());//here is the problem
},
'choice_attr' => function($user) {
return ['class' => 'user_'.strtolower($user->getUsername())];
},
])
->getForm();
return $this->render('default/showUsers.html.twig',
array('users' => $users, 'form' => $form->createView()));
}
I am sure $users gives an array with objects of the class User. When I execute the route in the browser I get following error message:
Error: Call to a member function getUsername() on a non-object
in src\AppBundle\Controller\DefaultController.php at line 50
Line 50 is the commented line return strtoupper($user->getUsername());
What is the problem and how can I solve?
And how can I get the selected User after I submitted via a submit button to the same route?
EDIT: (because of possible duplication)
Of course I know that the method getUsername() can not be called, because $user is a non-object, which should not be related to the Symfony documentation. So my question relates to a Symfony special solution which has absolutly nothing to do with 100 of other problems where the Error is the same.
Use entity type instead. Here is a link to documentation. It's a child type of a choice type, with exactly same functionality, and also every option returns an entity object.
Because I had trouble with setting up the entity type field too, I decided to post my solution for the whole action function and the twig file here:
action method:
/**
* #Route("/showUsers", name="showUsers")
*/
public function showUsersAction(Request $request){
// gets array of all users in the database
$users = $this->getDoctrine()->getManager()->getRepository('AppBundle:User')->findAll();
$form = $this->createFormBuilder()
->setMethod('POST')
->add('users', EntityType::class, array(
'class' => 'AppBundle:User',
'choices' => $users,
// This combination of 'expanded' and 'multiple' implements radio buttons
'expanded' => true,
'multiple' => false,
'choice_label' => function ($user) {
return $user->__toString();
}
))
// Adds a submit button to the form.
// The 'attr' option adds a class to the html rendered form
->add('selected', SubmitType::class, array('label' => 'Show User Items', 'attr' => array('class' => 'button')))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// gets the selected user
$selectedUser = $form["users"]->getData();
// redirects to route 'showItems' with the id of the selected user
return $this->redirectToRoute('showItems', array('userId' => $selectedUser->getId()));
}
// renders 'default/showUsers.html.twig' with the form as an argument
return $this->render('default/showUsers.html.twig', array('form' => $form->createView()));
}
twig file:
{#
// app/Resources/views/default/showUsers.html.twig
Description:
twig file that implements a form in which one of all users can get
selected via radio button to show the items of the user after a click
on the submit button.
#author goulashsoup
#}
{% extends 'base.html.twig' %}
{% block title %}Users{% endblock %}
{% block body %}
<div class="users">
{{ form_start(form) }}
{% for user in form.users %}
{{ form_widget(user) }}
{{ form_label(user) }}
<br>
{% endfor %}
<br>
{{ form_end(form) }}
</div>
{% endblock %}

Symfony2 Sonata admin dynamically change input data based on selected value

For one of my objects I need to create some dynamic form rendering... But I cant figure out how to do this in Sonata Admin. For example when I create an object I have a field type. In this field I select a type that my object is going to be. Now when I select the type I want to make a field appear, based on the type. For example, if I select type "Carousel" I want to show a field that is selecting all object form entity Gallery. If I select type "Product" I want to display field with all products to selectt from... How can I acheve that?
Right now I have this:
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Module', array(
'class' => 'col-md-6'
))
->add('position')
->add('type', null, array(
'attr' => array('class' => 'module_type')
))
->add('items', 'entity', array(
'class' => 'ApplicationSonataMediaBundle:Gallery'
))
->end()
;
}
And I have overriden the edit template:
{% extends 'SonataAdminBundle:CRUD:edit.html.twig' %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
$(document).ready(function () {
$(".module_type").change(function() {
});
});
</script>
{% endblock %}
As you can see the gallery is hardcoded now..
I cant figure out how to do this now... How to say that if the value selected is this, use that entity in field... The problem is that the way form is rendered in Sonata is very complicated... I dont understand it..
maybe I should use ajax? But again, when I send a value and get the response how to add a field without refresh?
Any help appreciated.
After researching forever to find a way to use dynamic dropdowns using ajax and sonata with symfony4 i would like to share my solution how it actually works for me.
In my case i have a district and this district has different cities. I have a company that first chooses a district and then depending of that it´s city.
What i did:
Create your entity for the districts
Create your entity for the cities
Go in your main class (in my case this is the company entity and add two entities for the districts and cities
/**
* #ORM\ManyToOne(targetEntity="App\Wdm\MainBundle\Entity\Model\Cities", inversedBy="id")
*/
private $city;
/**
* #ORM\ManyToOne(targetEntity="App\Wdm\MainBundle\Entity\Model\Districts", inversedBy="id")
*/
private $district;
Update your database schema and fill the cities and districts fields with some example data
Go into your AdminClass in the configureFormFields Function and add the following (make sure to use the 'choice_label' option correctly with the corresponding field in your district or city entity.
protected function configureFormFields(FormMapper $formMapper)
{ $formMapper
// Some other added fields
->add('district', EntityType::class, [
'choice_label' => 'name',
'class' => Districts::class,
'placeholder' => '',
])
->add('city', EntityType::class, [
'choice_label' => 'name',
'class' => Cities::class,
'placeholder' => '',
])
;
This should already work pretty well. You should now be able to have a dependent field. Let´s take a look to the AJAX magic now.
Go into your AdminClass (same as the configureFields-Class) and add the following
protected function configureRoutes(RouteCollection $collection)
{ $collection->add('reloadCities', 'reload-cities');
}
Now you have a route that you can access from your ajax url. Now create a new Controller Class, wherever you want...
<?php
namespace App\Wdm\MainBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sonata\AdminBundle\Controller\CRUDController as BaseController;
use App\Wdm\MainBundle\Entity\Model\Cities;
class CitiesController extends BaseController
{
public function reloadCitiesAction(Request $request)
{ $districtid = $request->request->get('id');
$cities = $this->getDoctrine()->getRepository(Cities::class)->findBy(array("district" => $districtid));
return $this->render("company/cities.html.twig", array("cities" => $cities));
}
}
... and don´t forget to register this controller in your services.yaml...
admin.company:
class: App\Wdm\MainBundle\Admin\CompanyAdmin
arguments:
- ~
- App\Wdm\MainBundle\Entity\Model\Company
- App\Wdm\MainBundle\Controller\CitiesController (THIS IS THE NEW ROW)
... and finally the little template that is being called in this function...
// THIS IS THE cities.html.twig
{% for city in cities %}
<option value="{{ city.id }}">{{ city.name }}</option>
{% endfor %}
So far so good. We now got the logic that get´s the data from the ajax call and returns it to your sonata admin edit form. The only thing thats missing now is the jquery code that is needed in the edit template of sonata admin.
Go into your AdminClass and insert the following code (e.g. before configureFormFields)
public function getTemplate($name)
{
switch ($name) {
case 'edit':
return 'company/cities_admin.html.twig';
break;
default:
return parent::getTemplate($name);
break;
}
}
Now we create this cities_admin.html.twig template that overrides the default template
{% extends 'SonataAdminBundle:CRUD:edit.html.twig' %}
{% block form %}
{{ parent() }}
<script type="text/javascript">
$(document).ready(function () {
$("#ID_OF_YOUR_DISTRICT_SELECT_FIELD").change(function () {
$.ajax({
url: "{{ admin.generateUrl('reloadCities') }}",
data: {
'id': $(this).val(),
'uniquid': '{{ admin.uniqid }}'
},
method: 'POST',
success: function (html) {
$("#ID_OF_YOUR_CITY_SELECT_FIELD").html(html);
},
error: function (data) {
// more code
}
});
});
});
</script>
{% endblock %}
That´s it. Should work like a charm.
Sonata provides you with the 'sonata_type_choice_field_mask' type which allows you to change the fields displayed on the form dynamically depending on the value of this 'sonata_type_choice_field_mask' input so you don't have to use ajax.
Here is the doc where you can find everything about sonata types and the choice field mask.
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('type', 'sonata_type_choice_field_mask', array(
'choices' => array(
//The list of available 'Type' here
'choice1',
'choice2'
),
'map' => array(
//What you want to display depending of the selected option
'choice1' => array(
// List of the fields displayed if choice 1 is selected
'field1', 'field3'
),
'choice2' => array(
// List of the fields displayed if choice 2 is selected
'field2', 'field3'
)
),
'placeholder' => 'Choose an option',
'required' => true
))
->add('field1', 'entity', array(/* Options for entity1 goes here */))
->add('field2', 'entity', array(/* Options for entity2 goes here */))
->add('field3')
;
}
If I have realy understand your need you have to use ajax of course, first you need to add new admin route to this EntityAdminController to do it you have to override the configureRoutes method and to add your new route like this :
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('reloadCities', 'realod-cities');
}
Then you have to create a new controller which gonna have the action definition for your new route like :
use Sonata\AdminBundle\Controller\CRUDController as BaseController;
class CitiesController extends BaseController
{
public function reloadCitiesAction()
{
// some code
return $this->render(...);
}
}
Then you have to override the SonataAdminBundle:CRUD:edit.html.twig template and set your javascript event listener like this :
{% extends 'SonataAdminBundle:CRUD:edit.html.twig' %}
{% block form %}
{{ parent() }}
<script type="text/javascript">
$(document).ready(function () {
countries.change(function () {
$.ajax({
url: "{{ admin.generateUrl('reloadCities') }}",
data: {
'id': $(this).val(),
'uniquid': '{{ admin.uniqid }}'
},
method: 'POST',
success: function (html) {
// code...
},
error: function (data) {
// more code
}
});
});
});
</script>

Symfony 2 Form collection field with type file

I want to upload multiple files with POST request (without Ajax). Can I use Symfony 2's form collection field with type file like this:
Code in Entity:
public $pictures;
public function __construct()
{
$this->pictures = new \Doctrine\Common\Collections\ArrayCollection();
}
Code in Form Class:
$builder->add('pictures', 'collection', array(
'type' => 'file',
'required' => false,
'attr' => array(
'multiple' => 'multiple'
)
));
Code in Twig:
{% for picture in form.pictures %}
<td>
{{ form_widget(picture) }}
</td>
{% endfor %}
I tried, but it doesn't seem to work. It is not showing any errors, but it is not showing the input file either. Any ideas?
To actually render input types you will need to set the allow_add option in the collection to true and use the form prototype of the collection, javascript and a button to add file fields.
An example based in the documentation (Collection- adding and removing)
The form:
<form action="..." method="POST" {{ form_enctype(form) }}>
{# ... #}
{# store the prototype on the data-prototype attribute #}
<ul id="image-container" class="collection-container" data-prototype="{{ form_widget(form.images.vars.prototype) | e }}">
{% for imageField in form.images%}
<li>
{{ form_widget(imageField) }}
</li>
{% endfor %}
</ul>
Add image
</form>
The script:
<script type="text/javascript">
var imageCount;
jQuery(document).ready(function() {
$(document).on('click', '.collection-add', function () {
var $collectionContainer = $('#' + $(this).data('collection'));
if(!imageCount){imageCount = $collectionContainer.children().length;}
var prototype = $collectionContainer.attr('data-prototype');
var item = prototype.replace(/__name__/g, imageCount);
$collectionContainer.append(item);
imageCount++;
});
})
</script>
This is just an idea, there is still plenty to do depending on your needs.
If this wasn't what you were looking for, maybe you could call the add button click as a workaround.
If you want to show multiple input fields, no, it won't work. A collection type requires you to supply some data before rendering the fields. I've already tried it, and came up creating a separate entity (e.g. File) and and adding relationship to my target entity.
example:
class File
{
// properties
public $file; // this holds an UploadedFile object
// getters, setters
}
FileType:
....
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', 'file', [
'required' => false
])
;
}
Product:
class Product
{
// properties
private $images; // ManyToMany relationship
// setters, getters
}
ProductType:
->add('images', 'collection', [
'type' => 'YOUR_FILE_TYPE_NAME',
'by_reference' => false,
'required' => false
])
Product contoller:
public function someAction()
{
...
$image = new File();
$product->addImage($image);
}
I know this solution can be overkill and creates extra tables, but it works.
You need to specify the type of the widget in collection
Look at the: http://symfony.com/doc/2.0/reference/forms/types/collection.html
$builder->add('pictures', 'collection', array(
// each item in the array will be an "email" field
'type' => 'file',
// these options are passed to each "email" type
'options' => array(
'required' => false,
),
));
For further reading I suggest
http://symfony.com/doc/2.0/reference/forms/types/file.html
Also you need to add sth to this collection to display cuz it will be empty when initialize like in your constructor of entity.
The code in the form class is correct, but you also should modify the name of the formfield. The full_name should be form[pictures][] in your case.
It actually seems that modifying a form field name is not possible anymore in sf2.3, but it will be possible in sf2.4 again.
https://github.com/bamarni/symfony/commit/35639824e864ed8d4a4cc0d8360f2c73ae08b507#commitcomment-3627879

Categories