this question may sound a bit newbie. What I am trying to do is to show a form inside a view, create an object and submit it.
My list view shows a list of existing objects and I showed a new/create button that brings the form which creates a new object calling the controller.
I don't know if there is a more proper way to do this but I came up with this code that works partially. The code works when my url is /new (showing only the form), however while it is /list it doesn't work.
List view (only relevant code):
{# AppBundle/Resources/views/list.html.twig #}
<div id="info">
<button type="button" onClick="new()">New Object</button>
</div>
<script>
function new() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
document.getElementById("info").innerHTML = xhttp.responseText;
}
}
xhttp.open("GET", "/new", true);
xhttp.send();
}
</script>
New view:
{# AppBundle/Resources/views/new.html.twig #}
{{ form(form) }}
Controller methods:
// AppBundle/Controller/ObjectController
public function newAction(Request $request)
{
$object = new Object();
$form = $this->createFormBuilder($object)
->add('Text', 'textarea')
->add('save', 'submit')
->getForm();
$form->handleRequest($object);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($object);
$em->flush();
return $this->redirectToRoute('list');
}
return $this->render('AppBundle:new.html.twig', array(
'form' => $form->createView(),
));
}
public function listAction(Request $request)
{
$objects = $this->getDoctrine()
->getRepository('AppBundle\Entity\Object')
->findAll();
return $this->render('AppBundle:list.html.twig', array(
'objects' => $objects,
));
}
Thanks a lot for your time!!
No data are displayed in list.html.twig. You have to add this kind of code:
/.../
{% for object in objects %}
{{ object.text }}
{% endfor %}
<div id="info">
<button type="button" onClick="new()">New Object</button>
</div>
Related
I'm trying to clear my form after the form submit but unfortunately, it doesn't work
Here's my controller.
#[Route('/', name: 'homepage', methods: ['GET', 'POST'])]
public function index(Request $request, FooRepo $fooRepo): Response
{
$foo = new Foo();
$form = $this->createForm(FooType::class, $foo);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$fooRepo->save($foo, true);
if (TurboBundle::STREAM_FORMAT === $request->getPreferredFormat()) {
// If the request comes from Turbo, set the content type as text/vnd.turbo-stream.html and only send the HTML to update
$request->setRequestFormat(TurboBundle::STREAM_FORMAT);
return $this->render('shared/success.html.twig', ['status' => "Success!"]);
}
return $this->redirect($this->generateUrl("homepage"));
}
return $this->render('home/index.html.twig', [
controller_name' => 'HomeController',
'form' => $form
]);
}
So far, I tried the unset and redirect from this How can I clear form values after successful form submission but no success.
Here's my sample UI:
{{ form_start(form) }}
<input type="text" name=" {{ field_name(form.name) }} " placeholder="enter name"/>
<button type="submit">Save</button>
{{ form_end(form) }}
I'm using turbo that's why there's no page reload when I hit submit. There's no issue in saving but when the ajax call finished, the field doesn't clear after the submission. Any help?
I am working with Symfony2 and I am trying to create new entity Collection without page reload. My controller works fine, but I have issues with Ajax, I am NOT well familiar with it. Below is the code I already have. When I press submit button, new entity is saved in db, but entity doesn't appear on page.
CollectionController
/**
* #Route("/create-collection", name="collection_create_collection")
* #Template()
*/
public function createAction(Request $request)
{
$collection = new Collection();
$form = $this->createForm(
new CollectionType(),
$collection,
array('method' => 'POST',
'action' => $this->generateUrl('collection_create_submit'),
)
);
return array('collection'=>$collection, 'form' => $form->createView());
}
/**
* #Route("/collection_create_submit", name="collection_create_submit")
*/
public function createSubmitAction(Request $request)
{
$collection = new Collection();
$user = $this->getUser();
$form = $this->createForm(
new CollectionType(),
$collection,
array('method' => 'POST',
)
);
$form->handleRequest($request);
if ($form->isValid() && $form->isSubmitted()) {
$colname = $form["name"]->getData();
$existing = $this->getDoctrine()->getRepository('CollectionBundle:Collection')->findBy(['name' => $colname, 'user' => $user]);
if ($existing) {
return new JsonResponse(['error' => 'Collection with such name already exists']);
}
$em = $this->getDoctrine()->getManager();
$em->persist($collection);
$em->flush();
return new JsonResponse(array(
'success' => $collection
));
}
}
create.html.twig
{% include 'CollectionBundle:Collection:collectionJS.html.twig' %}
<div class="collection-create">
<h3 id="create-collection">Create a collection</h3>
<a class="close-reveal-modal" aria-label="Close">×</a>
{{ form_start(form, {'attr' : {'action' : 'collection_create_collection', 'id': 'create-collection-form'}}) }}
{{ form_widget(form) }}
<a class="button custom-close-reveal-modal" aria-label="Close">Cancel</a>
<button type="submit" value="create" class="right" onclick="createCollection();">Create</button>
{{ form_end(form) }}
</div>
<script type="application/javascript">
$('a.custom-close-reveal-modal').on('click', function(){
$('#externalModal').foundation('reveal', 'close');
});
</script>
collectionJS.html.twig
function createCollection(){
var $form = $('#create-collection-form');
$($form).submit(function(e) {
e.preventDefault();
$.ajax({
type: "POST",
url: $form.attr('action'),
data: $form.serialize()
}).done(function( data ) {
if(data.error)
{
console.log(data.error);
} else if(data.success) {
var collection = data.success;
$('.griditems').prepend('<p>' + collection + '</p>');
$('#externalModal').foundation('reveal', 'close');
}
});
});
}
UPD
The submit is triggered, but now I get undefined instead of entity. Probably, I am sending wrong json response.
UPD
I tried to encode collection entity.
In createSubmitAction I changed return to
$jsonCollection = new JsonEncoder();
$jsonCollection->encode($collection, $format = 'json');
return new JsonResponse(array(
'success' => $jsonCollection
));
How do I get this response in Ajax?
JsonResponse cannot transform your entities into JSON. You need to use a serializer library like JMSSerializerBundle or implement a serializing-method inside your entity.
Check the answers provided here.
How to encode Doctrine entities to JSON in Symfony 2.0 AJAX application?
You have to return a simple $collection object as the standard doctrine entity objects are too large in size. What I recommend is:
return new JsonResponse(array(
'success' => $collection->toArray()
));
and adding a new method to your entity class:
public function toArray(){
return array(
'id' =>$this->getId(),
'name'=>$this->getName(),
// ... and whatever more properties you need
); }
I have on my form template new.html.twig a select where all the elements of an entity are loaded ($categories).
Controller
public function newAction(Request $request){
$entity = new Playlist();
$form = $this->createCreateForm($entity);
$em = $this->getDoctrine()->getManager();
$categories = $em->getRepository('PublicartelAppBundle:Category')->getAllCategories();**
return $this->render('PublicartelAppBundle:Playlist:new.html.twig', array(
'categories' => $categories,
'entity' => $entity,
'form' => $form->createView(),
));
}
Select filter that serves to make a search.
Select in my twig template that displays all categories obtained from the query.
new.html.twig
<div class="form-group">
<label for="publicartel_appbundle_category_name">Search for category: </label>
<select id='selectCategory' class="form-control select2">
{% for category in categories %}
<option>
{{ category.name }}
</option>
{% endfor %}
</select>
</div>
JS in my twig template that detects changes in select option to make a new query and retrieve records from the selected option.
JS code
$('#selectCategory').on('change', function () {
var optionSelect = $(this).val();
$.post("{{path('playlist_category') }}", {category: optionSelect},
function (filterContent) {
for (var content in filterContent.category_result) {
for (var name in filterContent.category_result[content]) {
var result = filterContent.category_result[content][name];
console.log(result);
$('#playlist_content_name').append('<option>' + result + '</option>');
}
}
}, 'json');
});
The result of the new query is done in the playlist_category route is assigned to another select this in my twig template with the append method.
<div class="form-group">
<label for="publicartel_appbundle_area_name">Content</label>
{{ form_widget(form.items.vars.prototype.content, { 'id': 'playlist_content_name', 'full_name': '', required: false, 'attr': {'class': 'form-control select2'}}) }}
</div>
This select load other options, but the other select is used to filter options to prevent all options be loaded.
My problem is that when I try to send the form with any of the options obtained from the search, I get an error that the variable categories does not exist in my template twig new.html.twig.
Then, as the method createAction, also conducts a rendering of the template new.html.twig also add the variable $ categories there.
Controller
public function createAction(Request $request){
$entity = new Playlist();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
if ($form->isValid()) {
// $em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('playlist_show', array(
'id' => $entity->getId())));
}
$categories = $em->getRepository('PublicartelAppBundle:Category')->getAllCategories();
return $this->render('PublicartelAppBundle:Playlist:new.html.twig', array(
'categories' => $categories,
'entity' => $entity,
'form' => $form->createView(),
));
}
But when complete the form, having chosen an option of select which return the search, instead of addressing the playlist_show route, directs me to the path /create with the form new.html.twig.
I have Two Action, GetAllPost and newComment
I have a page with many Post and each Post have commentForm
PostController
public function getPostAction () {
return array(
);
}
Twig
{% for post in app.user.posts %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ render(controller("ADVCommentBundle:Comment:newComment" ,{ 'id': post.id,'redirect':'get_post' } )) }}
<hr>
{%endfor%}
CommentController
public function newCommentAction (Request $request, Post $post) {
$em = $this->getEm();
$comment = new Comment();
$form = $this->createForm(new CommentType(), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
}
return array(
'post' => $post,
'form' => $form->createView(),
);
}
TwifFormController
{{ form(form, {'action': path('new_comment',{'id': post.id})})}}
When I insert a new comment I have redirect to new_comment even if my value isn't valid.
How can I redirect to GeTAllPost and show the correct Error or the new Comment?
I tried to use
return $this->redirect($this->generateUrl('get_post',array('error',$form->getErrors())));
and 'error_bubbling' => true,, but each time request a get_post ( GetAllPost ) I do a new render of my Form and I don't see the errors
For Example i'd like to use newCommentAction in several scenario.
For example i GetAllPost for each post, but even in GetSpecificPost, where I Have A specific post, where I Can insert a new comment, but the save ( and the Action ) is the same.
Do I have create a Service ?
UPDATE
After Bonswouar's answer. This is my Code
PostController
/**
* #Route("/",name="get_posts")
* #Template()
*/
public function getPostsAction () {
$comment = new Comment();
return array(
'commentForms' => $this->createCreateForm($comment),
);
}
private function createCreateForm (Comment $entity) {
$em = $this->getEm();
$posts = $em->getRepository('ADVPostBundle:Post')->findAll();
$commentForms = array();
foreach ($posts as $post) {
$form = $this->createForm(new CommentType($post->getId()), $entity);
$commentForms[$post->getId()] = $form->createView();
}
return $commentForms;
}
/**
* #Method({"POST"})
* #Route("/new_comment/{id}",name="new_comment")
* #Template("#ADVPost/Post/getPosts.html.twig")
* #ParamConverter("post", class="ADVPostBundle:Post")
*/
public function newCommentAction (Request $request, Post $post) {
$em = $this->getEm();
$comment = new Comment();
//Sometimes I Have only One Form
$commentForms = $this->createCreateForm($comment);
$form = $this->createForm(new CommentType($post->getId()), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
} else {
$commentForms[$post->getId()] = $form->createView();
}
return array(
'commentForms' => $commentForms,
);
}
And I Don't have any Render.
But, I want to re-use newCommentAction also in Single Post, and i Want to create Only one Form. I don't want use $commentForms = $this->createCreateForm($comment);, because i Want just one form,and I have to change template even. How can I do ?
If I'm not mistaking, your problem is that you're posting on new_comment, which is a "sub action".
You actually don't need this Twig render.
You could just generate all the forms you need in the main Action, with something like this :
foreach ($posts as $post) {
$form = $this->createForm(new CommentType($post->getId()), new Comment());
$form->handleRequest($request);
if ($form->isValid()) {
//...
// Edited : to "empty" the form if submitted & valid. Another option would be to redirect()
$form = $this->createForm(new CommentType($post->getId()), new Comment());
}
$commentForms[$post->getId()] = $form->createView();
}
return array(
'posts' => $posts,
'commentForms' => $commentForms,
);
Not forgetting to set a dynamic Name in your Form class :
class CommentType extends AbstractType
{
public function __construct($id) {
$this->postId = $id;
}
public function getName() {
return 'your_form_name'.$this->postId;
}
//...
}
And then just normally render your forms in your Twig loop. You should get the errors.
{% for post in app.user.posts %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ form(commentForms[post.id]) }}
<hr>
{%endfor%}
If I didn't miss anything that should do the job.
UPDATE :
After seeing your update, this might be the controller you want (sorry if I didn't understand properly or if I did some mistakes):
/**
* #Route("/",name="get_posts")
* #Template()
*/
public function getPostsAction () {
$em = $this->getEm();
$posts = $em->getRepository('ADVPostBundle:Post')->findAll();
$commentForms = array();
foreach ($posts as $post) {
$commentForms[$post->getId()] = $this->createCommentForm($post);
}
return array(
'commentForms' => $commentForms
);
}
private function createCommentForm (Post $post, $request = null) {
$em = $this->getEm();
$form = $this->createForm(new CommentType($post->getId()), new Comment());
if ($request) {
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
$form = $this->createForm(new CommentType($post->getId()), new Comment());
}
}
return $form;
}
/**
* #Method({"POST"})
* #Route("/new_comment/{id}",name="new_comment")
* #Template("#ADVPost/Post/getPosts.html.twig")
* #ParamConverter("post", class="ADVPostBundle:Post")
*/
public function newCommentAction (Request $request, Post $post) {
return array(
'commentForm' => $this->createCommentForm($post, $request);
);
}
What about using a flash message to set your error messages? http://symfony.com/doc/current/components/http_foundation/sessions.html#flash-messages
EDIT: Modifying based on your comments. In your controller you could do this:
foreach ($form->getErrors() as $error) {
$this->addFlash('error', $post->getId().'|'.$error->getMessage());
}
or
$this->addFlash('error', $post->getId().'|'.(string) $form->getErrors(true, false));
What this will do is allow you to tie the error to the particular post you want, as you are passing it a string like 355|This value is already used. If you need to know the field you could add another delimiter for $error->getPropertyPath() in your flash message, or you could overwrite the error name in the entity itself.
Then in your controller you could parse out the flash messages, and add them to an array that your twig template would check:
$errors = array();
foreach ($this->get('session')->getFlashBag()->get('error', array()) as $error)
{
list($postId, $message) = explode('|', $error);
$errors[$postId][] = $message;
}
return array('errors' => $errors, ...anything else you send to the template)
Now your twig template can check for the existence of errors on that particular form:
{% for post in app.user.posts %}
{% if errors[post.id] is defined %}
<ul class="errors">
{% for error_message in errors[post.id] %}
<li>{{ error_message }}</li>
{% endfor %}
</ul>
{% endif %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ render(controller("ADVCommentBundle:Comment:newComment" ,{ 'id': post.id,'redirect':'get_post' } )) }}
<hr>
{%endfor%}
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 %}