Symfony5 - formData object send by javascript XMLHttpRequest not Submited in controller - javascript

I am trying to save images when dropped over a canvas (saving the position it is dropped to, it source ...etc). To acheive this each image is considered an object that has a saveObject methode that will POST a formData object threw an XMLHttpRequest to a Symfony5 controller, that will receive the formData in a symfony Form.
The symfony form is binded to the the entity representing the objects in the canvas, and should save the formData content to the database on submit.
The problem I am facing is that the controller seem's not to consider the form as submitted.
I have checked : the formData object does contain all the data before the XMLHttpRequest, but on the controller side all the fields seem to be set to null.
this is the saveObject methode :
saveObject() {
console.log("In saveObject");
var xhr = new XMLHttpRequest();
var FD = new FormData;
// set FormData values
for (name in this) {
if (name === 'album_id') {
FD.append(name, this.parent_album);
} else {
FD.append(name, this[name])
}
}
//used to check if FD contains the data it is supposed to
for (var key of FD.entries()) {
console.log(key[0] + ', ' + key[1]);
}
xhr.open('POST', '/edit/storeObjects/');
xhr.send(FD);
xhr.onload = function () {
if (xhr.status != 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else {
console.log("subission ok");
console.log(xhr.response);
}
}
}
And this is the controller receiving the request :
/**
* #Route ("/edit/storeObjects/", name="save_object")
*/
public function saveObj(Request $request, EntityManagerInterface $em, UploaderHelper $uploaderHelper, SluggerInterface $slugger)
{
$albumObj = new AlbumObjects();
$user = $this->getUser();
$form = $this->createForm(SaveObjectFormType::class);
$form->handleRequest($request);
if ($request->isMethod('POST')) {
$form->submit($request->request->get($form->getName()));
if ($form->isSubmitted() && $form->isValid()) {
/** #Var AlbumObjects $albumObj * */
$albumObj = $form->getData();
if (true == $form['destImg']->getData()) {
$destImg = $form['destImg']->getData();
$user_dest = $user->getOwneravatar();
$newFilename = $uploaderHelper->uploadProfilePicture($user_dest, $destImg, $slugger);
$albumObj->setDestImg($newFilename);
}
$em->persist($albumObj);
$em->flush();
return $this->json([
'saveStatus' => 'Saved'
]);
}
}
return $this->json([
'saveStatus' => 'Not saved',
'albumObj' => $albumObj
]);
}
I am obviously doing something wrong, and would appreciate a hint to what it is I'm missing

I had most of this wrong but with some advice from a friend and some time it is now working, so I am posting her the modifications I have made in case anybody else is having the same issue.
First the form wasn't validated nor submitted because of the csrf protection included in symfony forms.
To go around this the advice I got and that worked pretty well was instead of a plain xmlhttprequest to post the data to the form : do first a get request and on this get return from the form the csrf token.
Then when creating the formData object in javascript it need's to take as parameter the form's name attribute.
Finally, the image wasn't being submitted in the controller though it was sent to the controller (also I checked the enctype was correct). I worked around this by getting the image directly in the Request object. This solution though doesn't "feel right"
so this is how I changed the javascript xmlhttprequest part :
saveObject() {
let xhr = new XMLHttpRequest();
xhr.open('GET', '/edit/storeObjects/');
xhr.send();
xhr.onload = function () {
if (xhr.status !== 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else {
var formName = document.createElement('form');
formName.setAttribute('name', JSON.parse(xhr.response)['formName']);
var fD = new FormData(formName);
var attrListe = ['parentAlbum','pageNumber','type','dx','dy','dwidth','dheight','sx','sy','swidth','sheight','destImg','containedText','fontSize', 'fontType','fontColor','textMaxWidth','textLineHeight','album']
attrListe.forEach(attrName => {
if (attrName == 'destImg') {
var file = this.dataURLtoFile(this.destImg, 'img');
fD.append(attrName, file);
} else {
fD.append(attrName, this[attrName])
}
});
fD.append('_token', JSON.parse(xhr.response)['token']);
for (var value of fD.entries()) {
console.log(value[0] + ': ' + value[1]);
}
let xhrPost = new XMLHttpRequest();
xhrPost.open('POST', '/edit/storeObjects/');
xhrPost.send(fD);
xhrPost.onload = function () {
if (xhrPost.status !== 200) {
alert(`Error ${xhrPost.status}: ${xhrPost.statusText}`);
} else {
console.log("Object Saved")
}
}
}
}.bind(this)
}
And here are the modifications to the controller :
public function saveObj(Request $request, EntityManagerInterface $em, UploaderHelper $uploaderHelper, SluggerInterface $slugger)
{
$user = $this->getUser();
$form = $this->createForm(SaveObjectFormType::class);
$form->handleRequest($request);
if ($request->isMethod('POST')) {
$form->submit($request->request->all());
if ($form->isSubmitted() && $form->isValid()) {
/** #Var AlbumObjects $albumObj * */
$albumObj = $form->getData();
//Check if the image field contains a file and if it does process it
if ($request->files->get('destImg')) {
$dest_Img = $request->files->get('destImg');
$user_dest = $user->getOwneravatar();
$newFilename = $uploaderHelper->uploadProfilePicture($user_dest, $dest_Img, $slugger);
$albumObj->setDestImg($newFilename);
}
$em->persist($albumObj);
$em->flush();
return $this->json([
'saveStatus' => 'Saved',
]);
}
}
return $this->json([
'formName' => $form->createView()->vars['name'],
'token' => $form->createView()->children['_token']->vars['value']
]);
}

Related

Getting the auto incremented id in return of my POST request?

I want to create a team and POST it to my database through the REST service i created, which is working.
BUT i need the id in return becuase i have to add it in another POST request, so what is the best way to get auto incremented id in return? am i missing some easy solution?
/*Post Base Request */
function postAjax(url, data, success) {
var params = typeof data == 'string' ? data : Object.keys(data).map(
function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) }
).join('&');
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xhr.open('POST', url);
xhr.onreadystatechange = function() {
if (xhr.readyState>3 && xhr.status==200) { success(xhr.responseText); }
};
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(params);
return xhr;
}
/* Funtion used onClick "create team" */
function createTeam(){
var teamToSend = {
teamName: document.getElementById('name').value
}
const convertTomemberIdString = JSON.stringify(teamToSend);
postAjax('https://xxx.azurewebsites.net/Service1.svc/teams',convertTomemberIdString, function(data){ console.log(data);})
console.log(`${status}`);
}
Two options:
Have it returned from your server as a response to this request.
Maintain a counter variable, and increment it when the request is successful. (Not persistent as you'll lose the value on page refresh)

My idea was create my own js method that i will pass in three parameters (method,url,data)

Js method
I have create a js post method using xmlhttprequest posting data to my
php page. The problem is how i will get the data after post request is
successful and display on my html page Below is my code.
I am currently working on a project that i have to use js method to post,get delete other more. All I want is an idea of how to get the data deplayed when the submit form function is called after successful submission of the data.
The code is working the part but the feedback part
//define form variables
var formSubmition = document.getElementById('formSubmition');
//addEventListener
formSubmition.addEventListener('submit',dataSubmit,true);;
function pageView(){
//localstorage_cookie = localStorage setItem('userpage_id','27727');
}
// connect to server via XMLHttpRequest
function serverConnection(){
this.object = {
method:'',
url:'',
data:'',
connect:function(a,b,d){
if(window.XMLHttpRequest){
server_http = new XMLHttpRequest();
}else{
server_http = new ActiveXObject('Microsoft.XMLHTTP');
}
server_http.onprogress = function(event){
console.log(event);
}
server_http.onreadystatechange = function(e){
if(server_http.readyState == 4 && server_http.status == 200){
var res = server_http.responseText;
return res;
}
}
server_http.open(a,b,true);
//server_http.setRequestHeader("Content-type","application/x-www-
form-urlencoded");
server_http.send(d);
}
}
}
var conn = new serverConnection(),response_xmlhttp = new serverConnection();
function serverConn(c,method,URI,formdata){
//defining data configurations
c.object['method'] = method; //defined key data for method
c.object['url'] = URI; //defined key data for url
c.object['data'] = formdata; //defined key data for data
//store in global variables
var method,url,data;
method = c.object['method'];
url = c.object['url'];
data = c.object['data'];
c.object.connect(method,url,data);
}
//submit_data using the class conn
function dataSubmit(e){
e.preventDefault();
var formdata,method,URI,username,password;
//define username and password
username = document.getElementById('username').value;
password = document.getElementById('password').value;
if(username == '' && password == ''){
alert('fill in all the fields');
}else{
formdata = new FormData(this);
formdata.append('submit_data','SIGNIN');
method = 'POST';
URI = 'admin/php_data/loginCredentials.php';
serverConn(conn,method,URI,formdata);
}
}
You need to pass a call back function to serverConnection which it can call when it receives the data from backend, something alone the lines of
function serverConnection(cb){
this.object = {
method:'',
url:'',
data:'',
connect:function(a,b,d){
if(window.XMLHttpRequest){
server_http = new XMLHttpRequest();
}else{
server_http = new ActiveXObject('Microsoft.XMLHTTP');
}
server_http.onprogress = function(event){
console.log(event);
}
server_http.onreadystatechange = function(e){
if(server_http.readyState == 4 && server_http.status == 200){
var res = server_http.responseText;
cb(res);
return res;
}
}
server_http.open(a,b,true);
//server_http.setRequestHeader("Content-type","application/x-www-
form-urlencoded");
server_http.send(d);
}
}
}
and then create a function to handle response
function handleResponse(res) {
// set the res in some element
}
Now just pass it to server connection
var conn = new serverConnection(handleResponse)

XMLHttpRequest add custom header

I would like to pass access_token via HTTP header.
_xhr.setRequestHeader('x-customtoken', 'value');
When I want to get it on server it's value is null. I get it like this:
public final static String HEADER_SECURITY_TOKEN = "x-customtoken";
// ..
request.getHeader(HEADER_SECURITY_TOKEN)
Pass the token via header is the only solution for me. I can pass it like a request param, but I need to to it via HTTP-headers.
xhr: function(options){
var _xhr = new XMLHttpRequest(),
_to_send = options.params;
_xhr.open(options.type, options.url);
_xhr.setRequestHeader('Content-Type', 'application/json');
_xhr.setRequestHeader('x-customtoken', 'value');
_xhr.responseType = 'json';
_xhr.onload = function () {
if (_xhr.status === 200) {
var _json = _xhr.response;
if (typeof _json == 'string')
_json = JSON.parse(_json);
options.success(_json);
} else {
options.error(_xhr);
}
};
try {
_xhr.send(_to_send);
} catch (e){
options.error(_xhr);
}
}

Uploading a file with FormData and multer

I have successfully managed to upload a file to a Node server using the multer module by selecting the file using the input file dialog and then by submitting the form, but now I would need, instead of submitting the form, to create a FormData object, and send the file using XMLHttpRequest, but it isn't working, the file is always undefined at the server-side (router).
The function that does the AJAX request is:
function uploadFile(fileToUpload, url) {
var form_data = new FormData();
form_data.append('track', fileToUpload, fileToUpload.name);
// This function simply creates an XMLHttpRequest object
// Opens the connection and sends form_data
doJSONRequest("POST", "/tracks/upload", null, form_data, function(d) {
console.log(d);
})
}
Note that fileToUpload is defined and the url is correct, since the correct router method is called. fileToUpload is a File object obtained by dropping a file from the filesystem to a dropzone, and then by accessing the dataTransfer property of the drop event.
doJSONRequest is a function that creates a XMLHttpRequest object and sends the file, etc (as explained in the comments).
function doJSONRequest(method, url, headers, data, callback){
//all the arguments are mandatory
if(arguments.length != 5) {
throw new Error('Illegal argument count');
}
doRequestChecks(method, true, data);
//create an ajax request
var r = new XMLHttpRequest();
//open a connection to the server using method on the url API
r.open(method, url, true);
//set the headers
doRequestSetHeaders(r, method, headers);
//wait for the response from the server
r.onreadystatechange = function () {
//correctly handle the errors based on the HTTP status returned by the called API
if (r.readyState != 4 || (r.status != 200 && r.status != 201 && r.status != 204)){
return;
} else {
if(isJSON(r.responseText))
callback(JSON.parse(r.responseText));
else if (callback !== null)
callback();
}
};
//set the data
var dataToSend = null;
if (!("undefined" == typeof data)
&& !(data === null))
dataToSend = JSON.stringify(data);
//console.log(dataToSend)
//send the request to the server
r.send(dataToSend);
}
And here's doRequestSetHeaders:
function doRequestSetHeaders(r, method, headers){
//set the default JSON header according to the method parameter
r.setRequestHeader("Accept", "application/json");
if(method === "POST" || method === "PUT"){
r.setRequestHeader("Content-Type", "application/json");
}
//set the additional headers
if (!("undefined" == typeof headers)
&& !(headers === null)){
for(header in headers){
//console.log("Set: " + header + ': '+ headers[header]);
r.setRequestHeader(header, headers[header]);
}
}
}
and my router to upload files is the as follows
// Code to manage upload of tracks
var multer = require('multer');
var uploadFolder = path.resolve(__dirname, "../../public/tracks_folder");
function validTrackFormat(trackMimeType) {
// we could possibly accept other mimetypes...
var mimetypes = ["audio/mp3"];
return mimetypes.indexOf(trackMimeType) > -1;
}
function trackFileFilter(req, file, cb) {
cb(null, validTrackFormat(file.mimetype));
}
var trackStorage = multer.diskStorage({
// used to determine within which folder the uploaded files should be stored.
destination: function(req, file, callback) {
callback(null, uploadFolder);
},
filename: function(req, file, callback) {
// req.body.name should contain the name of track
callback(null, file.originalname);
}
});
var upload = multer({
storage: trackStorage,
fileFilter: trackFileFilter
});
router.post('/upload', upload.single("track"), function(req, res) {
console.log("Uploaded file: ", req.file); // Now it gives me undefined using Ajax!
res.redirect("/"); // or /#trackuploader
});
My guess is that multer is not understanding that fileToUpload is a file with name track (isn't it?), i.e. the middleware upload.single("track") is not working/parsing properly or nothing, or maybe it simply does not work with FormData, in that case it would be a mess. What would be the alternatives by keeping using multer?
How can I upload a file using AJAX and multer?
Don't hesitate to ask if you need more details.
multer uses multipart/form-data content-type requests for uploading files. Removing this bit from your doRequestSetHeaders function should fix your problem:
if(method === "POST" || method === "PUT"){
r.setRequestHeader("Content-Type", "application/json");
}
You don't need to specify the content-type since FormData objects already use the right encoding type. From the docs:
The transmitted data is in the same format that the form's submit()
method would use to send the data if the form's encoding type were set
to multipart/form-data.
Here's a working example. It assumes there's a dropzone with the id drop-zone and an upload button with an id of upload-button:
var dropArea = document.getElementById("drop-zone");
var uploadBtn = document.getElementById("upload-button");
var files = [];
uploadBtn.disabled = true;
uploadBtn.addEventListener("click", onUploadClick, false);
dropArea.addEventListener("dragenter", prevent, false);
dropArea.addEventListener("dragover", prevent, false);
dropArea.addEventListener("drop", onFilesDropped, false);
//----------------------------------------------------
function prevent(e){
e.stopPropagation();
e.preventDefault();
}
//----------------------------------------------------
function onFilesDropped(e){
prevent(e);
files = e.dataTransfer.files;
if (files.length){
uploadBtn.disabled = false;
}
}
//----------------------------------------------------
function onUploadClick(e){
if (files.length){
sendFile(files[0]);
}
}
//----------------------------------------------------
function sendFile(file){
var formData = new FormData();
var xhr = new XMLHttpRequest();
formData.append("track", file, file.name);
xhr.open("POST", "http://localhost:3000/tracks/upload", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error(xhr.statusText);
}
}
};
xhr.send(formData);
}
The server side code is a simple express app with the exact router code you provided.
to post a FormData object accepted by multer the upload function should be like this:
function uploadFile(fileToUpload, url) {
var formData = new FormData();
//append file here
formData.append('file', fileToUpload, fileToUpload.name);
//and append the other fields as an object here
/* var user = {name: 'name from the form',
email: 'email from the form'
etc...
}*/
formData.append('user', user);
// This function simply creates an XMLHttpRequest object
// Opens the connection and sends form_data
doJSONRequest("POST", "/tracks/upload", null, formData, function(d) {
console.log(d);
})
}

Problems inheriting XMLHttpRequest / ActiveXObject classes

I have the following JavaScript class:
var Server = function(onError)
{
/* public As, onError; */
var that, Key, Headers;
this.__construct = function()
{
that = this;
that.As = false;
that.onError = onError;
that.resetHeaders();
onError = null;
// Here I try to call the parent constructor (it seems I can't).
if(window.XMLHttpRequest)
that.XMLHttpRequest();
else
that.ActiveXObject('Microsoft.XMLHTTP');
}
this.Request = function(File, PostData, Function)
{
var Method, HeaderKey;
if(PostData == null)
Method = 'GET';
else
Method = 'POST';
try
{
that.open(Method, File, that.As);
/* Each request sets X-Requested-With to XMLHttpRequest by default.
If PostData is given, then we treat it's content type as a form.*/
that.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
if(PostData != null)
that.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
for(HeaderKey = 0; HeaderKey < Headers.length; HeaderKey++)
that.setRequestHeader(Headers[ HeaderKey ].Name, Headers[ HeaderKey ].Value);
if(Function != null)
that.onreadystatechange = function()
{
if(that.readyState == 4 && that.status == 200)
Function.call();
}
that.send(PostData);
}
catch(Exception)
{
if(that.onError != null)
that.onError(Exception);
}
}
this.addHeader = function(Name, Value)
{
Headers[ Key ] = {};
Headers[ Key ].Name = Name;
Headers[ Key ].Value = Value;
Key++;
}
this.resetHeaders = function()
{
Headers = [];
Key = 0;
}
this.__construct();
}
if(window.XMLHttpRequest)
Server.prototype = new XMLHttpRequest();
else
Server.prototype = new ActiveXObject('Microsoft.XMLHTTP');
Server.prototype.constructor = Server;
Where I make an inheritance depending on the state of the window.XMLHttpRequest var. In the __construct method I re-check this again for call the parent constructor.
I don't know if this is the correct form, but I would like that anyone could tell me what's wrong in here. By the way, when I check the console in Chrome I get the following exception: "Uncaught TypeError: Object [object Object] has no method 'XMLHttpRequest'", so I assume that it is not identifying the correct reference, however, I can see that all the properties / methods are present when I put the "." in the console, but I can't get access from a internal / external way (this is commenting the parent constructor condition). Thank you, I'll wait for your replies.

Categories

Resources