Submit form using javascript - AJAX - symfony

I have article entity, and I want to add one article via ajax.
I create a form:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('descriptions');
}
This is my controller:
/**
* #Route("/admin/article/add", name="app_admin_add_article")
*/
public function addCustomer(Request $request)
{
$form = $this->createForm(ArticleFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
/** #var Article $article */
$article = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($article);
$em->flush();
$this->addFlash('success', 'Article was successfully added!');
return $this->redirectToRoute('app_admin_articles');
}
return $this->render('admin/articles/create.html.twig', [
'articleForm' => $form->createView(),
]);
}
How can I write the javascript to submit this form via ajax?

Call your ajax on your html using a button or another element
$.ajax({
type: 'POST',
url: "{{ url('app_admin_add_article') }}",
data: {
name: document.getElementById("nameInput").value,
description: document.getElementById("descriptionInput").value
},
success: function(data) {
alert(data);
},
error: function(err) {
alert(err);
}
});
and get the data sent by ajax in your controller
/**
* #Route("/admin/article/add", name="app_admin_add_article")
*/
public function addCustomer(Request $request)
{
// throw exception if not ajax call
if (!$request->isXmlHttpRequest()) {
throw new NotFoundHttpException();
}
$name = $request->request->get('name');
$description = $request->request->get('description');
$article = new Article();
$article->setName($name);
$article->setDescription($description);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($article);
$entityManager->flush();
return new Response('Article created');
}
This is just a basic example without validating the input values and considering your class extends Controller

i guess you ajax request should look somthing like this :
$.ajax({
type: 'POST',
url: $("form").attr("action"),
data: $("form").serialize(),
//or your custom data either as object {foo: "bar", ...} or foo=bar&...
success: function(response) { ... },
});
and you have to fire it with on click function on the submit button after preventing default for the button :
e.preventDefault();
and in this case you have to change your controller logic.
but if i may ask why do you want to submit with ajax ?

Related

Symfony post submit event doesnt modify dynamic field

Here is my case:
I have two fields in a form, the second field values depends on the value selected on the first field.
I'm doing this to modify the second field:
$builder->add('firstField',EntityType::class, array(class => First::class));
$formModifier = function(FormInterface $formBuilder, $data = null){
if($data != null){
$formBuilder->add('secondField', EntityType::class, array(
class => Second::class,
'query_builder' => function (EntityRepository $er) use ($data) {
return $er->createQueryBuilder('s')
->where('s.field = :data')
->setParameter('data', $data)
;
});
} else {
$formBUilder->add('secondField', HiddenType::class, array('required' => false);
}
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getData());
}
);
$builder->get('firstField')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier){
$data = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $data);
}
);
Ajax:
var $firstField = $('#firstField');
$(document).on('change', '#firstField', function(){
var $form = $(this).closest('form');
var data = {};
data['id'] = $(this).val();
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
success: function (html) {
$('#secondField').html(
$(html).find('#secondField').html()
);
}
})
});
The data is sent correctly via ajax, but i never get the correct response because it doesn't ever enter in the POST_SUBMIT event listener as i tried to debug and use some var_dump to check it. Any help would be appreciated. Thanks.

Silex - get right condionals in finish middleware

I want to create a pdf file out of some route-dependant data
{http://example.com/products/123/?action=update}
$app->finish(function (Request $request, Response $response) {
// Make a pdf file, only if:
// - the route is under /products/
// - the action is update
// - the subsequent ProductType form isSubmitted() and isValid()
// - the 'submit' button on the ProductType form isClicked()
});
As a normal form submission process I have:
public function update(Application $app, Request $request)
{
$form = $app['form.factory']->create(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if (!$form->get('submit')->isClicked()) {
return $app->redirect('somewhere');
}
$product = $form->getData();
$app['em']->persist($product);
$app['em']->flush();
return $app->redirect('product_page');
}
return $app['twig']->render('products/update.html.twig', array(
'form' => $form->createView(),
));
}
Questions:
Should I duplicate all of the conditionals in finish middleware?
How to access the Product entity in finish middleware?
Consider having multiple resource types like Products, Services, Users, ...

Symfony2 add field to form dynamically when onChange occurs

I'm starting to investigate Symfony2 framework and it seems that I've messed up some things in my mind.
I am trying to do a relatively simple thing; Dynamically add an extra field to a form depending on the value of a select box. Let's assume for simplicity that I don't have my form attached to any entity, so what I eventually want to get back is an array of values.
So, as a simple example I would have this controller:
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$form = $this->createFormBuilder(null, array('allow_extra_fields' => true))
->add('test', 'Symfony\Component\Form\Extension\Core\Type\TextType')
->add('select', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array(
'choices' => array(
'Maybe' => null,
'Yes' => true,
'No' => false,
),
'choices_as_values' => true,
))
->add('submit', 'Symfony\Component\Form\Extension\Core\Type\SubmitType')
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
dump($form->getData());
die;
}
return $this->render('default/index.html.twig', array(
'form' => $form->createView()
));
}
/**
* #Route("/test", name="test")
*/
public function AddFieldAction(Request $request)
{
return new JsonResponse($request->get('form'));
}
And the javascript of the view could be:
$('document').ready(function(){
$('#form_select').change(function(){
var form = $(this).closest('form');
var value = $(this).val();
var data = {};
data[$(this).attr('name')] = value;
$.ajax({
url: "{{ url("test") }}",
type: 'POST',
data: data,
dataType: 'json'
}).done(function (response) {
console.log(response);
if (response.select == 1) {
$('#form').append("<label> extra field </label> <input type='text' id='extra_field' name="form[extra_field]"></input>");
}
}).fail(function (jqXHR, textStatus) {
});
})
})
Now this would return the field correctly, as this would invoke the AddFieldAction and it would add the appropriate HTML in the form. When i would try to submit the form, in the $form->isValid part, I could get all the submitted data with the $form->getData() and $form->getExtraData() methods.
Question is, is this the optimal way to do it? Should (and could) I use Form Events instead? I was trying to use form events but it becomes a mess and I get lost easily in the hierarchy of PRE_SUBMIT or PRE_SET_DATA conficting each other.

How to remove the null value field from form in symfony form handler

I want to update the data in two conditions:
When user enters all the fields in form (Name, email, password)
When user does not enter password (I have to update only name & email).
I have the Following formHandler Method.
public function process(UserInterface $user)
{
$this->form->setData($user);
if ('POST' === $this->request->getMethod()) {
$password = trim($this->request->get('fos_user_profile_form')['password']) ;
// Checked where password is empty
// But when I remove the password field, it doesn't update anything.
if(empty($password))
{
$this->form->remove('password');
}
$this->form->bind($this->request);
if ($this->form->isValid()) {
$this->onSuccess($user);
return true;
}
// Reloads the user to reset its username. This is needed when the
// username or password have been changed to avoid issues with the
// security layer.
$this->userManager->reloadUser($user);
}
An easy solution to your problem is to disable the mapping of the password field and copy its value to your model manually unless it is empty. Sample code:
$form = $this->createFormBuilder()
->add('name', 'text')
->add('email', 'repeated', array('type' => 'email'))
->add('password', 'repeated', array('type' => 'password', 'mapped' => false))
// ...
->getForm();
// Symfony 2.3+
$form->handleRequest($request);
// Symfony < 2.3
if ('POST' === $request->getMethod()) {
$form->bind($request);
}
// all versions
if ($form->isValid()) {
$user = $form->getData();
if (null !== $form->get('password')->getData()) {
$user->setPassword($form->get('password')->getData());
}
// persist $user
}
You can also add this logic to your form type if you prefer to keep your controllers clean:
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormInterface $form) {
$form = $event->getForm();
$user = $form->getData();
if (null !== $form->get('password')->getData()) {
$user->setPassword($form->get('password')->getData());
}
});
Easier way:
/my/Entity/User
public function setPassword($password)
{
if ($password) {
$this->password = $password;
}
}
So, any form using User with password will act as expected :)

symfony2 chained selectors

I have three entities: Country, State and City with the following relationships:
When creating a City, I want two selectors, one for the Country and one for the State where the city belongs. These two selectors need to be chained so changing the Country will "filter" the States shown in the other selector.
I found a tutorial showing how to do this using Form Events but their example it's not quite my case. My entity City it's not directly related to the Country entity (they are indirectly related through State) so, when setting the country field in the City form (inside the class CityType), I'm forced to declare that field as 'property_path'=>false as you can see in the code below:
class CityType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('country', 'entity', array(
'class'=>'TestBundle:Country',
'property'=>'name',
'property_path'=>false //Country is not directly related to City
));
$builder->add('name');
$factory = $builder->getFormFactory();
$refreshStates = function ($form, $country) use ($factory)
{
$form->add($factory->createNamed('entity', 'state', null, array(
'class' => 'Test\TestBundle\Entity\State',
'property' => 'name',
'query_builder' => function (EntityRepository $repository) use ($country)
{
$qb = $repository->createQueryBuilder('state')
->innerJoin('state.country', 'country');
if($country instanceof Country) {
$qb->where('state.country = :country')
->setParameter('country', $country);
} elseif(is_numeric($country)) {
$qb->where('country.id = :country')
->setParameter('country', $country);
} else {
$qb->where('country.name = :country')
->setParameter('country', "Venezuela");;
}
return $qb;
}
)));
};
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (DataEvent $event) use ($refreshStates)
{
$form = $event->getForm();
$data = $event->getData();
if($data == null)
return;
if($data instanceof City){
if($data->getId()) { //An existing City
$refreshStates($form, $data->getState()->getCountry());
}else{ //A new City
$refreshStates($form, null);
}
}
});
$builder->addEventListener(FormEvents::PRE_BIND, function (DataEvent $event) use ($refreshStates)
{
$form = $event->getForm();
$data = $event->getData();
if(array_key_exists('country', $data)) {
$refreshStates($form, $data['country']);
}
});
}
public function getName()
{
return 'city';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Test\TestBundle\Entity\City');
}
}
The problem is that when I try to edit an existing City, the related Country is not selected by default in the form. If I remove the line 'property_path'=>false I get (not surprisingly) the error message:
Neither property "country" nor method "getCountry()" nor method "isCountry()" exists in class "Test\TestBundle\Entity\City"
Any ideas?
OK, I finally figured out how to do it properly:
namespace Test\TestBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Event\DataEvent;
use Test\TestBundle\Entity\Country;
use Test\TestBundle\Entity\State;
use Test\TestBundle\Entity\City;
class CityType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
$factory = $builder->getFormFactory();
$refreshStates = function ($form, $country) use ($factory) {
$form->add($factory->createNamed('entity','state', null, array(
'class' => 'Test\TestBundle\Entity\State',
'property' => 'name',
'empty_value' => '-- Select a state --',
'query_builder' => function (EntityRepository $repository) use ($country) {
$qb = $repository->createQueryBuilder('state')
->innerJoin('state.country', 'country');
if ($country instanceof Country) {
$qb->where('state.country = :country')
->setParameter('country', $country);
} elseif (is_numeric($country)) {
$qb->where('country.id = :country')
->setParameter('country', $country);
} else {
$qb->where('country.name = :country')
->setParameter('country', null);
}
return $qb;
})
));
};
$setCountry = function ($form, $country) use ($factory) {
$form->add($factory->createNamed('entity', 'country', null, array(
'class' => 'TestBundle:Country',
'property' => 'name',
'property_path' => false,
'empty_value' => '-- Select a country --',
'data' => $country,
)));
};
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (DataEvent $event) use ($refreshStates, $setCountry) {
$form = $event->getForm();
$data = $event->getData();
if ($data == null) {
return;
}
if ($data instanceof City) {
$country = ($data->getId()) ? $data->getState()->getCountry() : null ;
$refreshStates($form, $country);
$setCountry($form, $country);
}
});
$builder->addEventListener(FormEvents::PRE_BIND, function (DataEvent $event) use ($refreshStates) {
$form = $event->getForm();
$data = $event->getData();
if(array_key_exists('country', $data)) {
$refreshStates($form, $data['country']);
}
});
}
public function getName()
{
return 'city';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Test\TestBundle\Entity\City');
}
}
The jQuery AJAX selector
$(document).ready(function () {
$('#city_country').change(function(){
$('#city_state option:gt(0)').remove();
if($(this).val()){
$.ajax({
type: "GET",
data: "country_id=" + $(this).val(),
url: Routing.generate('state_list'),
success: function(data){
$('#city_state').append(data);
}
});
}
});
});
I hope this will be helpful to somebody else facing the same situation.
Since your link to this approach is down i decided to complement your excelent answer so anyone can use it:
In order to execute the following javascript command:
url: Routing.generate('state_list'),
You need to install FOSJsRoutingBundle that can be found in here.
ATENTION: in the read me section of the bundle there are instalation instructions but there is something missing. If you use the deps with this:
[FOSJsRoutingBundle]
git=git://github.com/FriendsOfSymfony/FOSJsRoutingBundle.git
target=/bundles/FOS/JsRoutingBundle
You must run the php bin/vendors update before the next steps.
I'm still trying to find out what route is needed in the routing.yml for this solution to work. As soon as i discover i will edit this answer.
You will need a dedicated FieldType for the chained selectbox. And also an xhr controller that can return child options based on passed parameter. Ofcourse property_path should be set to false.

Resources