Symfony2: Save Record From Ajax Form Submission - ajax

I am totally lost. There's only so much documentation one can read before it all starts making zero sense.
I want to be able to save form data passed from outside of my Symfony application. I have already installed FOSRestBundle, JMSSerializerBundle, NelmioCorsBundle, etc.
First off, I have a FormType that looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('requestDate')
->add('deliverDate')
->add('returnDate')
->add('created')
->add('updated')
->add('contentChangedBy')
;
}
Then I have a REST controller containing the POST method which is supposed to store the new record:
class AvRequestController extends Controller
{
...
public function postAvrequestAction(Request $request){
$entity = new AvRequest();
$form = $this->createForm(new AvRequestType(), $entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return new \Symfony\Component\HttpFoundation\JsonResponse($entity, Codes::HTTP_CREATED);
}
return new \Symfony\Component\HttpFoundation\JsonResponse($request, 400);
}
}
Here is the test with the mock ajax form data:
$('#postform').submit(function(event){
event.preventDefault();
console.log("submitted");
ajaxObject = {
url: $("#postform").attr("action"),
type: 'POST', // Can be GET, PUT, POST or DELETE only
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({"id":2, "title":"billabong", "requestDate":"2000-01-01 11:11:11", "deliverDate": "2000-01-01 11:11:11", "returnDate": "2000-01-01 11:11:11", "created": "2000-01-01 11:11:11", "updated": "2000-01-01 11:11:11", "content_changed_by":"cpuzzuol"})
};
// ... Add callbacks depending on requests
$.ajax(ajaxObject)
.done(function(data,status,xhr) {
console.log( two );
})
.fail(function(data,status,xhr) {
console.log( status );
})
.always(function(data,status,xhr) {
console.log( data );
});
console.log("END");
});
When I submit the form, the 400 Bad Request is tripped in my POST method. Worse, my $request bag is always empty:
{"attributes":{},"request":{},"query":{},"server":{},"files":{},"cookies":{},"headers":{}}
If I do
$request->getContent()
I get my stringified data:
"{\u0022id\u0022:2,\u0022title\u0022:\u0022billabong\u0022,\u0022requestDate\u0022:\u00222000-01-01 11:11:11\u0022,\u0022deliverDate\u0022:\u00222000-01-01 11:11:11\u0022,\u0022returnDate\u0022:\u00222000-01-01 11:11:11\u0022,\u0022created\u0022:\u00222000-01-01 11:11:11\u0022,\u0022updated\u0022:\u00222000-01-01 11:11:11\u0022,\u0022content_changed_by\u0022:\u0022cpuzzuol\u0022}"
I've read that this might have something to do with FOSRestBundle's "body listener" but I've already enabled that:
body_listener: true
UPDATE
body_listener doesn't seem to play a role at all. As the answer below states, you have to create a form with a blank name since the form you are submitting from outside of the system isn't going to have the name it would normally have if it were made inside of Symfony. Also, make sure to turn off CSRF if you don't have that set up at first.

Form isValid checks also for CSRF token validation. You can turn off csrf token validation in AvRequestType.
//...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\AvRequest',
'csrf_protection' => false
));
}
//...
Also, I suggest your form has name. isValid also checks for your form name.
// form without name
public function getName()
{
return '';
}
Or
$form = $this->get('form.factory')->createNamed('', new AvRequestType(), $avRequest);
If you want to create entity, you should send data without id(from JS).
I have used "JMS serializer" to serialize my entity to json.
//Controller
public function postAvRequestAction(Request $request)
{
$avRequest = new AvRequest();
$form = $this->createForm(new AvRequestType(), $avRequest);
$form->handleRequest($request);
$form = $this->get('form.factory')->createNamed('', new AvRequestType(), $avRequest);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($avRequest);
$em->flush();
$serializer = $this->get('serializer');
$serialized = $serializer->serialize($avRequest, 'json');
return new Response($serialized);
}
return new JsonResponse(array(
'errors' => $this->getFormErrors($form)
));
}
protected function getFormErrors(Form $form)
{
$errors = array();
foreach ($form->getErrors() as $error) {
$errors['global'][] = $error->getMessage();
}
foreach ($form as $field) {
if (!$field->isValid()) {
foreach ($field->getErrors() as $error) {
$errors['fields'][$field->getName()] = $error->getMessage();
}
}
}
return $errors;
}

Related

Symfony 5 / Request Response : Get data with Ajax

when I try to get data in ajax, the returned object is empty
I send the id of the data I want to get in js :
function selectMessage(id) {
$.ajax({
url: '{{ path('back_translation_update') }}',
method: 'GET',
data: {id: id}
}).done(function (response) {
console.log(response)
})
}
$('.updateMessage').click(function (evt) {
evt.stopPropagation()
selectMessage($(this).data('id'))
})
in the controller I look for the data to return :
/**
* #Route("/update", name="back_translation_update", methods="GET|POST")
*/
public function getById(Request $request): Response
{
if ($request->isXMLHttpRequest()) {
$id = $request->get('id');
// dd($id);
$message = $this->translationService->getTranslationById($id);
// return new JsonResponse(['data' => $message]);
$response = new Response();
$response->setContent(json_encode([
'data' => $message,
]));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
I use a service because with the repository I get an error: getById () must be an instance of Symfony\Component\HttpFoundation\Response
with :
$repositoryMessage = $this->em->getRepository(TranslationMessage::class);
$message = $repositoryMessage->findOneBy(['id' => $id]);
so the service will look in the database:
public function getTranslationById($translation_id)
{
$query = $this->em->createQueryBuilder()
->from(TranslationMessage::class,'message')
->select('message')
->where('message.id = ?1')
->setParameter(1, $translation_id);
$message = $query->getQuery()->getResult();
// dd($message);
return $message;
}
all the dd() give the expected values:
into getById(): the id of the row sought
into getTranslationById(): the sought object
but in the XHR, data contains an empty object: uh:
same with a new JsonResponse, commented here
what did I miss? help
Use Aurowire to get messageRepository object and use $this->json() to return JsonResponse
/**
* #Route("/update", name="back_translation_update", methods="GET|POST")
*/
public function getById(Request $request, TranslationMessageRepository $messageRepository): JsonResponse
{
$id = $request->query->get('id');
$message = $messageRepository->find($id);
if(!$message) { return new NotFoundHttpException(); }
return $this->json([
'success' => true,
'data' => $message
]);
}
Define success function instead of done function
function selectMessage(id) {
$.ajax({
url: "{{ path('back_translation_update') }}",
method: 'GET',
data: { id: id }
success: function(data) {
console.log(data)
}
})
}

Sf2 : FOS UserBundle : registration AJAX

I'm trying to register a user with AJAX.
I created an event listener on FOSUserEvents::REGISTRATION_SUCCESS
So I'm trying to know is an AJAX request has been made but the response on my client side doesn't satisfy me.
Here my event listener, note that the response sent is a test so of course there should be no "else" condition.
<?php
namespace SE\AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Ajax listener on FOS UserBundle registration
*/
class RegistrationListener implements EventSubscriberInterface
{
private $router;
public function __construct(RequestStack $RequestStack)
{
$this->requestStack = $RequestStack;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess'
);
}
public function onRegistrationSuccess()
{
$request = $this->requestStack->getCurrentRequest();
if ($request->isXmlHttpRequest()) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
}
else{
$array = array( 'success' => false ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
}
}
}
services.yml:
se.app.listener.registration:
class: SE\AppBundle\EventListener\RegistrationListener
arguments: ["#request_stack"]
tags:
- { name: kernel.event_subscriber }
javascript:
// Submit the request
$.ajax({
type : 'POST',
url : url,
data : data,
success : function(data, status, object) {
console.log('success');
console.log(data);
},
error: function(data, status, object){
console.log('error');
console.log(data);
}
});
Firstly the weird thing is that it goes in the error condition.
The console.log (data) is returned the DOM of the registration success page :
...
<p>Congrats brieuc.tribouillet7777#gmail.com, your account is now activated.</p>
...
So does this logic should be here or should I override the controller? What am I doing wrong?
Because of the level of the REGISTRATION_SUCCESS event, you can't return a response directly from the EventListener.
You need to grab the FormEvent and modify its response.
Lets pass it as argument:
class RegistrationListener implements EventSubscriberInterface
{
// ...
public function onRegistrationSuccess(FormEvent $event)
{
$request = $this->requestStack->getCurrentRequest();
// Prepare your response
if ($request->isXmlHttpRequest()) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
} else {
$array = array( 'success' => false ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
}
// Send it
$event->setResponse($response);
}
}
And it should work.
Note There is an issue about this event where the response cannot be modified.
If the problem occurs, you need to set a low priority in your event subscribing:
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_SUCCESS => [
['onRegistrationSuccess', -10],
],
];
}
See #1799.
EDIT
Note You should use a JsonResponse instead of json_encode your data and set the Content-Type manually.
To grab the form itself and its eventual errors, you can do this:
public function onRegistrationSuccess(FormEvent $event)
{
$form = $event->getForm();
if (count($validationErrors = $form->getErrors()) == 0) {
return $event->setResponse(new JsonResponse(['success' => true]));
}
// There is some errors, prepare a failure response
$body = [];
// Add the errors in your response body
foreach ($validationErrors as $error) {
$body[] = [
'property' => $error->getPropertyPath() // The field
'message' => $error->getMessage() // The error message
];
}
// Set the status to Bad Request in order to grab it in front (i.e $.ajax({ ...}).error(...))
$response = new JsonResponse($body, 400);
$event->setResponse($response);
}
But because it's a success event, you may need to override the method itself.

Ajax query works when adding new post but doesn't work when update an entity

I have two Select box one for the Countries and the second for the cities and the second one depends from the selected choice of the first one.
I the code is as the example Dynamic Generation for Submitted Forms in the documentation http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data
All things work fine when adding a new post , but when trying do update a post, the Ajax query to display cities doesn't work .
This is the controller
// newAction
/**
* #ParamConverter("agence", options={"mapping": {"agence_slug":"slug"}})
*/
public function newAction(Agence $agence, Request $request)
{
$em = $this->getDoctrine()->getManager();
$travel = new Travel();
$form = $this->createForm(new TravelType($agence), $travel);
if ($request->getMethod() == 'POST')
{
//....
}
return $this->render('AppBundle:Dashboard/Travel:new.html.twig',
array(
'form' => $form->createView() ,
'agence' => $agence,
));
}
//editAction
/**
* #ParamConverter("agence", options={"mapping": {"agence_slug":"slug"}})
* #ParamConverter("travel", options={"mapping": {"travel_id":"id"}})
*/
public function editAction(Travel $travel, Agence $agence, Request $request)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new TravelEditType($agence), $travel);
if ($request->getMethod() == 'POST'){
//....
}
return $this->render('AppBundle:Dashboard/Travel:edit.html.twig',
array(
'form' => $form->createView() ,
'travel' => $travel,
'agence' => $agence,
));
}
travelType and ti works good
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
//........
use Symfony\Component\Form\FormInterface;
use AppBundle\Entity\Country;
class TravelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//....
$formModifier = function (FormInterface $form, Country $country = null) {
$cities = null === $country ? array() : $country->getCities();
$form->add('destination', 'entity', array(
'class' => 'AppBundle:CityWorld',
'choices' => $cities,
'multiple' => false,
'expanded' => false,
'property' => 'name',
'label' => 'Destination',
'attr' => array('class' => 'col-xs-10 col-sm-10', 'placeholder' => 'Destination'),
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$formModifier($event->getForm(), $data->getCountry());
}
);
$builder->get('country')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$country = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $country);
}
);
$builder->addEventListener(FormEvents::POST_SUBMIT, function ($event) {
$event->stopPropagation();
}, 90000000000000); // Always set a higher priority than ValidationListener
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Travel'
));
}
public function getName()
{
return null;
}
}
This is TravelEditType
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
//........
use Symfony\Component\Form\FormInterface;
use AppBundle\Entity\Country;
class TravelEditType extends TravelType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options) ;
}
public function getName()
{
return null;
}
}
This is the form and javascript code
<form method="post" action="" class="form-horizontal" role="form" {{ form_enctype(form) }} >
//.............
</form>
<script type="text/javascript">
var $county = $('#country');
$county.change(function () {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
var data = {};
data[$county.attr('name')] = $county.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
success: function (html) {
// Replace current position field ...
$('#city').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#city')
);
// Position field now displays the appropriate positions.
}
});
});
Try it without parameter in URI:
dashboard_city_ajax:
path: /citiies/ajax
defaults: { _controller: AppBundle:TravelDashboard:ajaxCities }
And send data with POST:
$.ajax({
url: '{{ path('dashboard_city_ajax') }}',
type: 'POST',
data: { agence_slug: '{{ agenceSlug }}' },
You can receive it in controller:
$request->request->get('bar', 'default value if bar does not exist');
The problem is in your route matching. There is many way to solve problem. Try just include in your ajax route requirements for matching route or in $.ajax function use another route, for example
The simplest way for you (if you don't want rebuild your controller) is just rebuild your route and put in the first place your ajax route like :
dashboard_city_ajax:
path: /{ajax}/{agence_slug}/citiies
defaults: { _controller: AppBundle:TravelDashboard:ajaxCities }
requirements:
ajax: ajax
$.ajax({
url: '{{ path('dashboard_city_ajax', {'agence_slug': agence.slug, 'ajax': 'ajax' }) }}',
type: 'POST',
data: data,
But the right way IMHO is get your data from request. For example
vplanning_ajax:
path: /ajax
defaults: { _controller: VplanningPageBundle:Page:Ajax }
function getData(init) {
$.post(
{{ path('vplanning_ajax') }},
{
agence_slug: agence.slug,
yourdata2: 'yourdata2
} ,
function ( data ) {
handleData(data);
}
);
}
And in your Controller your just do
$agence_slug = $this->request->request->get('agence_slug');
$yourdata2 = $this->request->request->get('yourdata2);
...

Refresh page in symfony2 at the end of Ajax call

How can I refresh a page after an ajax call is executed.
My Ajax call is
$.ajax({
type: "POST",
data: data,
url:"{{ path('v2_pm_patents_trashpatents') }}",
cache: false
});
The method that is executed by this ajax is
public function trashpatentAction(Request $request){
if ($request->isXmlHttpRequest()) {
$patent_id = $request->get("pid");
$em = $this->getDoctrine()->getEntityManager();
$patent = $em->getRepository('MunichInnovationGroupPatentBundle:SvPatents')->find($patent_id);
if (!$patent) {
throw $this->createNotFoundException('No patent found for id '.$patent_id);
}
$patent->setIs_deleted(1);
$em->flush();
}
}
Edit
Portfolio Controller Index Action
public function indexAction(Request $request) {
$switch_form = $this->createForm(new SwitchPortfolioType($portfolios));
////rest of action code
return $this->render('MunichInnovationGroupPatentBundle:Portfolio:index.html.twig', array('new_patentgroup_form' => $new_patentgroup_form->createView(),'switch_form' => $switch_form->createView(), 'new_form' => $new_form->createView(), "portfolios" => $portfolios ,"selected_portfolio" => $selected_portfolio,"portfolio_groups" => $portfolio_groups,"patents" => $patents));
}
Index.html.twig
<form name="portfolios" action="{{ path('v2_pm_portfolio') }}" method="post" >
{{ form_widget(switch_form) }}
<input type="submit"class="portfolio_input button2 tooltip" value="Switch">
</form>
SwitchPortfolioType
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class SwitchPortfolioType extends AbstractType
{
public function __construct($portfolios)
{
$this->portfolios = $portfolios;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('', 'choice', array(
'choices' => array(
'test' => 'Test'
),
'empty_value' => 'Switch your Portfolio',
));
}
public function getName()
{
return 'switch_portfolio';
}
}
At the end of this action I want to refresh the page
How can I do this?
The refresh will have to be done in javascript. I am not sure why you would POST via ajax only to reload the page anyway, why not just POST normally?
Anyway, to do what you ask I would return some simple JSON response in the controller to signify a success and then in the ajax callback do this:
$.ajax({
type: "POST",
data: data,
url:"{{ path('v2_pm_patents_trashpatents') }}",
cache: false,
success: function(data) {
// optionally check if the response is what you wanted
//if (data.response == 'deleted') {
document.location.reload(true);
//}
}
});
Using jQUery:
$('your_form').disable=true;
Pure JS:
your_form.disabled = true;

Controller must return a response in Symfony2

I have an ajax call in my code. What I want to achieve with the call works fine. I want to delete some records from the database which is actually deleted when the method is called via ajax but as in symfony method it must return a response and thats why when the method is executed its gives me the error
My Ajax call is
$.ajax({
type: "POST",
data: data,
url:"{{ path('v2_pm_patents_trashpatents') }}",
cache: false,
success: function(){
document.location.reload(true);
}
});
And the method that is executed is
public function trashpatentAction(Request $request){
if ($request->isXmlHttpRequest()) {
$id = $request->get("pid");
$em = $this->getDoctrine()->getEntityManager();
$patent_group = $em->getRepository('MunichInnovationGroupPatentBundle:PmPatentgroups')->find($id);
if($patent_group){
$patentgroup_id = $patent_group->getId();
$em = $this->getDoctrine()->getEntityManager();
$patents = $em->getRepository('MunichInnovationGroupPatentBundle:SvPatents')
->findBy(array('patentgroup' => $patentgroup_id));
if($patents){
foreach($patents as $patent){
if($patent->getIs_deleted()==false){
$patent->setIs_deleted(true);
$em->flush();
}
}
}
$patent_group->setIs_deleted(true);
$em->flush();
}
else{
$em = $this->getDoctrine()->getEntityManager();
$patent = $em->getRepository('MunichInnovationGroupPatentBundle:SvPatents')->find($id);
if ($patent) {
$patent->setIs_deleted(1);
$em->flush();
}
}
return true;
}
}
How can I successfully return from this method ?
Any ideas? Thanks
Replace return true; with return new Response();. Also don't forget to write use Symfony\Component\HttpFoundation\Response; at the top.
You can also pass error code 200 and the content type like below.
return new Response('Its coming from here .', 200, array('Content-Type' => 'text/html'));
Here is the full example of the controller
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class WelcomeController extends Controller
{
public function indexAction()
{
return new Response('Its coming from here .', 200, array('Content-Type' => 'text/html'));
}
}

Resources