Symfony form validation from an AJAX render - javascript

I'm new to symfony, I render forms throughout a JSON so whenever I click on an icon it shows different forms (to change firstname, lastname...).
I return thoses forms as a JSON :
public function profileSettings()
{
$user = $this->getUser();
// Formulaire d'informations concernant le compte
$formAccountSettings = $this->createForm(AccountSettingsType::class, $user, [
'userEmail' => $user->getEmail(),
'userUsername' => $user->getUsername(),
]);
// Formulaire d'informations personnel
$formPersonnalSettings = $this->createForm(PersonnalSettingsType::class, $user, [
'userFirstname' => $user->getFirstname(),
'userLastname' => $user->getLastname(),
]);
// Retour en format JSON des 3 requêtes sous tableau
return $this->json(
[
'formAccountSettingsView' =>
$this->render('user/_accountSettings.html.twig', [
'form' => $formAccountSettings->createView(),
]),
'currentUser' => $user,
'formPersonnalSettingsView' => $this->render('user/_accountSettings.html.twig', [
'form' => $formPersonnalSettings->createView(),
]),
]
);
}
Here is how I display this :
$('#settings-user li').on('click', function (e) {
$.ajax({
type: 'GET',
url: "/website-skeleton/public/index.php/json/profile",
success: function (response) {
if ($(e.target).hasClass('profile')) {
$('.display').empty().append(
`
<p id="welcome">Bonjour, <em><strong>${response['currentUser']['username']}</strong></em> vous êtes enregistré sous le nom de <strong>${response['currentUser']['firstname']} ${response['currentUser']['lastname']}</strong>.
<br>
Votre adresse email est : <strong>${response['currentUser']['email']}</strong>.
<br>
Pour modifiez vos informations veuillez cliquez sur le menu de navigation.
</p>`);
} else if ($(e.target).hasClass('security')) {
$('.display').empty().append(response['formAccountSettingsView']['content']);
} else if ($(e.target).hasClass('informations')) {
$('.display').empty().append(response['formPersonnalSettingsView']['content'])
}
}
})
});
The problem now is that I don't know how to handle thoses forms from another controller and validate it with the constraints I set on my entity User this is how I validate :
public function editCredentials(Request $request, UserPasswordEncoderInterface $encoder)
{
$user = $this->getUser();
$formPersonnalSettings = $this->createForm(PersonnalSettingsType::class, $user);
if ($request->isMethod('POST')) {
if ($request->request->has('personnal_settings')) {
if ($formPersonnalSettings->isSubmitted() && $formPersonnalSettings->isValid()) {
$user->setFirstname($request->request->get('personnal_settings')['firstname']);
$user->setLastname($request->request->get('personnal_settings')['lastname']);
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$this->addFlash('personnal_success', 'Vos informations personnels ont bien été enregistrées !');
return $this->redirectToRoute('user_profile');
}
}
Is that a good method? should I handle everything with ajax ? Thanks for your time !

You should use the handlerequest which automatically sets the values in the form to the entity added to it once the form has been submitted.
$form = $this->createCreateForm($entity); // private function to create the form
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$em->persist($entity);
$em->flush();
}
}
return $response;
Once handled you just need to return an ok response, or return the form back to the view if something has gone wrong.
If I remember well, the asserts set on the entity are also automatically processed with the handleRequest(), it depends on how you declared them.
Yo can also add other errors after the submit, just add them between the isSumbmitted() and isValid(). Documentation
I leave you here some documentation that may help.
Handle request
Validation
Validation in the form itself

Related

How could I cast to DateTimeInterface from String?? Symfony Ajax

My real problem is that I am sending an Ajax Jquery Request to an API of mine, the server response is 'Argument #1 of ::setDate() must be DateTimeInterface, string given'
I tried to cast but it was useless.
My Ajax at JQUERY:
$.ajax({
type: "PUT",
url: "/api/distribucion",
data: JSON.stringify(dist),
dataType: "json",
success: function (response) {
console.log("mesa " + response.id + " actualizada");
}
});
My API at PHP (Symfony's controller):
public function putDistribucion(ManagerRegistry $mr, Request $request): Response
{
$datos = json_decode($request->getContent());
$datos = $datos->distribucion;
// Cogemos el ID de el Distribucion a editar
$id = $datos->id;
// Obtenemos el Distribucion
$distribucion = $mr->getRepository(Distribucion::class)->find($id);
// Cambiamos todos sus campos
$distribucion->setPosicionX($datos->pos_x);
$distribucion->setPosicionY($datos->pos_y);
$distribucion->setFecha($datos->fecha);
$distribucion->setMesaId($datos->mesa_id);
$distribucion->setAlias($datos->alias);
$distribucion->setReservada($datos->reservada);
$manager = $mr->getManager();
try {
// Lo mandamos a actualizar
$manager->persist($distribucion);
$manager->flush();
} catch (PDOException $e) {
$this->json(['message' => $e->getMessage(), "Success" => false], 400);
}
# Creado con éxito => Devolvemos la ID
return $this->json(
[
"message" => "Éxito al editar la distribucion " . $id,
"Success" => true
],
202 // Aceptado
);
}
The Object I'm sending:
Your entity Distribucion probably has a setter that look like this:
public function setFecha(DateTimeInterface $fecha): self
{
$this->fecha = $fecha;
return $this;
}
The setter is typehinted with DateTimeInterface so you need to use a DateTime object and not a string.
You may create a DateTime easily with DateTime::createFromFormat
So $distribucion->setFecha($datos->fecha); will turn into something like:
$distribucion->setFecha(DateTime::createFromFormat('YYYY-MM-DD HH:ii:ss.u', $datos->fecha));
You may want to verify the date format I used as the first parameter of DateTime::createFromFormat
public static DateTime::createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null)

Wordpress how to use Ajax to show new data returned from successful insert_post()?

After a successful Ajax insert of an entry, I would like to see what the ID and url of that same entry is and display it in a modal window without refreshing the page
Any way to get this data from success: function (response) {}? This is the code I have to make a new entry with ajax which works perfect:
<script>
$("#enquiry_email_form").on("submit", function (event) {
event.preventDefault();
var form= $(this);
var ajaxurl = form.data("url");
var detail_info = {
post_title: form.find("#post_title").val(),
post_description: form.find("#post_description").val()
}
if(detail_info.post_title === "" || detail_info.post_description === "") {
alert("Fields cannot be blank");
return;
}
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
post_details : detail_info,
action: 'save_post_details_form' // this is going to be used inside wordpress functions.php// *esto se utilizará dentro de las functions.php*
},
error: function(error) {
alert("Insert Failed" + error);
},
success: function(response) {
modal.style.display = "block"; * abre la ventana modal*
body.style.position = "static";
body.style.height = "100%";
body.style.overflow = "hidden";
}
});
})
</script>
<button id="btnModal">Abrir modal</button>
<div id="tvesModal" class="modalContainer">
<div class="modal-content">
<span class="close">×</span> <h2>Modal</h2> * Ventana modal mostrar le url y ID generado *
<p><?php ***echo $title_post, $url, $ID*** ?></p>
</div>
</div>
Archive funtions.php
function save_enquiry_form_action() {
$post_title = $_POST['post_details']['post_title'];
$post_description = $_POST['post_details']['post_description'];
$args = [
'post_title'=> $post_title,
'post_content'=>$post_description,
'post_status'=> 'publish',
'post_type'=> 'post',
'show_in_rest' => true,
'post_date'=> get_the_date()
];
$is_post_inserted = wp_insert_post($args);
if($is_post_inserted) {
return "success";
} else {
return "failed";
}
}
When you use wp_insert_postDocs function, it'll return the post id.
It returns the post ID on success. The value 0 or WP_Error on failure.
First you could initiate an empty array and call it, let's say, $response and populate it based on the returned value from wp_insert_post function.
Then, we could use the id to get the permalink as well, using get_permalinkDocs.
And at last, we could send that array back to the client-side by using wp_send_json_successDocs function.
So your code on the php side would be something like this:
function save_enquiry_form_action() {
$response = array(
'error' => '',
'success' => '',
'post_id' => '',
'post_url' => '',
);
$post_title = sanitize_text_field($_POST['post_details']['post_title']);
// Note we could have used 'sanitize_title()' function too!
$post_description = sanitize_textarea_field($_POST['post_details']['post_description']);
$args = array(
'post_title' => $post_title,
'post_content' => $post_description,
'post_status' => 'publish',
'post_type' => 'post',
'show_in_rest' => true,
'post_date' => get_the_date()
);
$post_id = wp_insert_post($args);
if($post_id){
$response['success'] = true;
$response['error'] = false;
$response['id'] = $post_id;
$response['post_url'] = get_permalink($post_id);
}else{
$response['success'] = false;
$response['error'] = true;
}
wp_send_json_success($response);
exit;
}
Note:
I've used sanitize_text_fieldDocs function to sanitize the $_POST['post_details']['post_title'] value and sanitize_textarea_fieldDocs function to sanitize the $_POST['post_details']['post_description'] value.
When you receive the response on the client-side, you could check for $response['success'] and $response['error'] values.
On the javascript side
As you can see on the following screenshot, data returns as data object. To access data you could use response.data.success, response.data.error, response.data.id and response.data.post_url.

Prevent InvalidArgumentException on dynamic form field creation

I'm following Symfony cookbook on dynamic form fields creation.
Basically, in my case, I have a Product, a ProductVersion and a Quantity field in my form.
On new forms, ProductVersion is hidden (only with a class attribute, It's still an EntityType).
On Product change (via select menu), I make an AJAX request to see if some ProductVersion exists for this product. If so, I populate the ProductVersion with available versions and show it to the user.
It's working fine with new forms. But when editing the same form, I have an InvalidArgumentException response on my AJAX request that tells me that the Quantity field is null :
Expected argument of type "int", "null" given at property path
"quantity".
I understand that indeed, I don't provide the quantity on my form submission through the AJAX request but that's the purpose of this method isn't it ? To only submit the field that makes a dynamic field change.
How can I do to avoid this Exception ?
Here is the ItemType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', EntityType::class, [
'label' => 'item.product',
'class' => Product::class,
'placeholder' => 'item.product',
]);
$formModifier = function (FormInterface $form, Product $product = null) {
if (null !== $product) {
$productVersions = $product->getVersions();
if (count($productVersions) > 0) {
$form->add('productVersion', EntityType::class, [
'class' => 'App\Entity\ProductVersion',
'placeholder' => 'item.product_version',
'choices' => $productVersions,
'label' => 'item.product_version'
]);
} else {
$form->add('productVersion', EntityType::class, [
'class' => 'App\Entity\ProductVersion',
'placeholder' => 'item.product_version',
'choices' => [],
'label' => 'item.product_version',
'disabled' => true,
'row_attr' => [
//'class' => 'd-none'
]
]);
}
} else {
$form->add('productVersion', EntityType::class, [
'class' => 'App\Entity\ProductVersion',
'placeholder' => 'item.product_version',
'choices' => [],
'label' => 'item.product_version',
'disabled' => true,
'row_attr' => [
//'class' => 'd-none'
]
]);
}
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$form = $event->getForm();
$data = $event->getData();
$options = $event->getForm()->getConfig()->getOptions();
//add custom product version according product selected
$formModifier($event->getForm(), $data->getProduct());
$form
->add('quantity', IntegerType::class, [
'label' => 'item.quantity',
]);
if ($data->getId()) {
$form
->add('save', SubmitType::class, [
'label' => $options['submit_btn_label'],
]);
} else {
$form
->add('save', SubmitType::class, [
'label' => $options['submit_btn_label'],
]);
}
}
);
$builder->get('product')->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)
$product = $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(), $product);
}
);
}
And here is the Javascript part :
$(document).ready(function () {
var $product = $('#item_product');
// When product gets selected ...
$product.on('change', function () {
console.log("product has changed")
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected product value.
var data = {};
data[$product.attr('name')] = $product.val();
// Submit data via AJAX to the form's action path.
console.log(data);
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
success: function (html) {
// Replace current position field ...
$('#item_productVersion').closest('.form-group').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#item_productVersion').closest('.form-group')
);
// Position field now displays the appropriate positions.
}
});
});
})
As #Jakumi suggested, adding an empty_data option to the quantity field solved the problem. Nevertheless, It gives an error on the quantity on the form received via the AJAX request and that's normal.
I found that this is not a "clean" method (you have to tweak form fields, you load the entire page on each AJAX request, etc..) so I decided to create a special route dedicated to form submissions that are meant to add/remove/edit fields.
This route creates a new form with preset fields (in my case the product selected by the user).
Then I can populate the other field (in my case the 'ProductVersion') with a regular PRE_SET_DATA Form Event.
This way :
- I don't have to worry about any other required field
- I can serialize the entire form in my AJAX request (no need to find the field, everything is done in the form builder)
- The AJAX request only output the form (I guess this improves performance a bit)
The form builder doesn't change (you still need to listen to PRE_SET_DATA and POST_SUBMIT. If you don't listen to the POST_SUBMIT, the form will not know about the choices you added dynamically and It will give you an error).
Here is the JS part :
$product.on('change', function() {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// serialize the entire form
var data = $form.serializeArray();
// Submit data via AJAX to the form's action path.
console.log(data);
$.ajax({
url : '/project/item/partial-edit', //custom route
type: $form.attr('method'),
data : data,
success: function(html) {
// Replace current position field ...
$('#item_productVersion').closest('.form-group').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#item_productVersion').closest('.form-group')
);
}
});
});
Here is the special route :
/**
* #Route("/item/partial-edit", name="edit_item_partial", requirements={"project"="\d+","item"="\d+"})
*/
public function edit_item_partial(Request $request)
{
$item = new Item();
$formOption = $request->get('option') ?? array(
'submit_btn_label' => 'update'
);
$form = $this->createForm(ItemType::class, $item, $formOption);
$form->handleRequest($request);
if ($form->isSubmitted()){
//if form was submitted, create a new form with the $item that has now a Product given
$newForm = $this->createForm(ItemType::class, $item, $formOption);
//here is the custom view only rendering the form
return $this->render('item/new_form-only.html.twig', [
'form' => $newForm->createView(),
]);
}
else {
return new Response('No form was submitted');
}
}

VueJS Laravel - Uploading image using Axios post error: 'The given data was invalid'

For school I'm making a blog with some people and now I want to upload an image to the blog post. But then this uploading the post the error:
{message: "The given data was invalid.", errors: {image: ["The image field is required."]}}
errors: {image: ["The image field is required."]}
message: "The given data was invalid."
I have searched on the internet and looked at other conversations, but I can't find my solution.
Here are my codes:
This is the form in a VueJS component:
<form method="post" action="./api/post" #submit.prevent="createPost()" enctype="multipart/form-data">
<p>Titel van de post</p>
<input type="text" placeholder="Titel van de post" name="title" v-model="title">
<p>Beschrijving bij de post</p>
<textarea placeholder="Beschrijving bij de post" name="description" v-model="description"/>
<input type="file" id="image" name="image" ref="image" v-on:change="(e) => {this.onChangeFileUpload(e)}"/>
<div class="uploadtimer">
<p>Selecteer een tijd wanneer de post moet worden getoond. (Niet verplicht)</p>
<datetime format="YYYY-MM-DD H:i" v-model="uploadTime"></datetime>
</div>
<input type="submit" class="input-submit">
</form>
The script in the component:
<script>
import datetime from 'vuejs-datetimepicker';
export default {
components: { datetime },
data() {
return {
title: '',
description: '',
uploadTime: '',
image: '',
}
},
methods: {
onChangeFileUpload(e) {
this.image = e.target.files[0];
},
createPost() {
let data = new FormData;
data.append('image', this.image);
axios.post('./api/post', {
title: this.title,
description: this.description,
data,
uploadTime: this.uploadTime,
}).then(response => {
}).catch(response => {
document.getElementById('errorMSG').style.display = 'flex';
document.getElementById('errorMSG').innerHTML = '<span>!</span><p>Er is iets mis gegaan met het plaatsen van de post.</p>';
setTimeout(function() {
document.getElementById('errorMSG').style.display = 'none';
}, 5000);
});
}
},
}
And the Controller function I'm using:
public function store(Request $request)
{
if(Auth::check()) {
$this->validate($request, [
'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg',
]);
$post = new Post();
$post->title = $request->input('title');
$post->description = $request->input('description');
$image = $request->file('image');
$imageExtension = $image->getClientOriginalExtension();
$imageName = time() . '_' . $imageExtension;
$image->move(public_path('/imgs/users-avatars/'), $imageName);
$post->image = '/imgs/posts-images/' . $imageName;
$post->uploadTime = $request->input('uploadTime');
$post->user_id = Auth::User()->id;
$post->save();
return (['message' => 'succes']);
} else {
return (['message' => 'error', 'handler' => 'Authenticated user is required!']);
}
}
How can I fix this?
You may use either image rule (that validates a file to be jpeg, png, bmp, gif, svg, or webp) or use mimes:jpeg,png,jpg,gif,svg:
So your validation would be either:
'image' => 'required|file|mimes:jpeg,png,jpg,gif,svg|max:2048',
Or:
'image' => 'required|file|image|max:2048',
Additionally, the file rule checks if the field is a successfully uploaded file and max:2048 rule checks if the file size is less than or equal to 2048 kilobytes (2MB). See Laravel docs for more info.
On VueJs, you have to append all your inputs to a FormData:
let data = new FormData;
data.append('image', this.image);
data.append('title', this.title);
data.append('description', this.description);
data.append('uploadTime', this.uploadTime);
axios.post('./api/post', data)
.then(
//...

How to keep dialogs showed up and alert nativescript

hi guys i am working on forget password in mobile app using nativescript, and i want to show alert message when the user insert wrong email or wrong apssword in the dialog , but i couldn't make it is there any body can help ?
let options: PromptOptions = {
title: "Entrez votre nouveau mot de passe",
okButtonText: "Suivant",
cancelButtonText: "Annuler",
cancelable: true,
inputType: inputType.password,
capitalizationType: capitalizationType.none
}; prompt(options).then((pass: PromptResult) => {
if (pass.text && pass.result == true) {
let options: PromptOptions = {
title: "Entrez votre nouveau mot de passe de nouveau",
okButtonText: "Suivant",
cancelButtonText: "Annuler",
cancelable: true,
inputType: inputType.password,
capitalizationType: capitalizationType.none
}; prompt(options).then((pass2: PromptResult) => {
if (pass2.text && pass2.result == true) {
if (pass.text == pass2.text) {
this.auth.resetMDP(this.email, this.code, pass2)
.then((res: any) => {
if (res.message == 'OK')
alert('votre mot de passe a été changé avec succès')
}).catch((err: any) => { alert(err); })
} else alert("re-entrez le mot de passe correctement s'il vous plait");
}
})
}
});
because i ve tried to make it work , but the dialog always close
In my apps I use modals to handle custom dialog styling and special action (as you require validation without closing the dialog). The easiest is:
This is how you call it:
page.showModal(
"you/path/to/modal/directory",
{
// your binding context
},
() => {
// on close callback
}
);
Assume that you have modal layout as XML and code behind (in my case Typescript):
<Page xmlns:RL="nativescript-ripple" xmlns="http://schemas.nativescript.org/tns.xsd" shownModally="onShownModally" >
<DockLayout stretchLastChild="false">
<Label dock="top" text="Hello Modal!!" horizontalAlignment="center" />
<TextView dock="top" text="{{ message }}" fontSize="15sp" horizontalAlignment="left" />
<Button dock="bottom" text="OK" tap="onOkTap" />
</DockLayout>
let closeCallback: () => void;
export function onShownModally(args: any) {
const page: Page = <Page>args.object;
const context = args.context;
// your close callback logic that you passed
closeCallback = args.closeCallback;
// bind you context object to modal
page.bindingContext = fromObject(context);
}
export function onOkTap(args: EventData) {
const page = args.object as Page;
if (closeCallback !== null && closeCallback !== undefined) {
closeCallback();
} else {
page.closeModal();
}
}

Categories