Send FileList in FormData in javascrirpt - javascript

I am trying to send a bunch of images using FormData but of course, I cannot send FileList and I was unsuccessful in figuring out a way to send these files. I have already tried to find answers on my own but most of them suggest appending each file.
for (let i = 0 ; i < images.length ; i++) {
formData.append("images[]", images[i]);
}
But it ends up with only the last file inside formData, for some reason previous files are being replaced by the next one in line.
I have also tried to convert FileList to an array, which works but I don't know how I could separate these files, right now every file is inside one key as a string.
0: "[{\"lastModified\":1606255989000,\"lastModifiedDate\":\"undefined\",\"name\":\"logo.png\",\"size\":54438,\"type\":\"image/png\"},{\"lastModified\":1606255979000,\"lastModifiedDate\":\"undefined\",\"name\":\"logo1.png\",\"size\":58023,\"type\":\"image/png\"},{\"lastModified\":1606252752000,\"lastModifiedDate\":\"undefined\",\"name\":\"logo2.png\",\"size\":28147,\"type\":\"image/png\"},{\"lastModified\":1606255121000,\"lastModifiedDate\":\"undefined\",\"name\":\"logo3.png\",\"size\":18260,\"type\":\"image/png\"}]"
I could just convert it to string and cut it to their own keys using } as and end of each entry. I don't want to do this, even with my little experience I know it's not a good way to go about it.
As of this moment, my javascript code looks like this.
File.prototype.toObject = function () {
return Object({
lastModified: parseInt(this.lastModified),
lastModifiedDate: String(this.lastModifiedDate),
name: String(this.name),
size: parseInt(this.size),
type: String(this.type)
})
}
FileList.prototype.toArray = function () {
return Array.from(this).map(function (file) {
return file.toObject()
})
}
let files = document.getElementById('files').files
let filesArray = files.toArray();
let formData = new FormData();
formData.append('images[]', JSON.stringify(filesArray));
I then send this data like this
fetch('<?=env('app.baseURL')?>/admin/gallery', {
method: 'POST',
processData: false,
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData,
data: formData,
}).then(function (response) {...rest of the code...})
After sending this data, which is received without any problems I want to save these files, but I cannot without first separating them. I am using PHP for the back-end.
I don't have much experience with javascript so I might be missing something obvious, any help is appreciated.

In your for loop you could use dynamic name for your formData appends. For example:
formData.append(`image-${i}`, images[i])
Or in case you want to push your images to a single key value pair in your formData, like in your example, you should use the getAll() method to retrieve your images.
formData.getAll('images[]')

Here is the PHP code to go through the array containing the images :
<?php
if(isset($_FILES) && isset($_FILES['images'])){
$countfiles = count($_FILES['images']['name']);
for($i=0;$i<$countfiles;$i++){
$filename = $_FILES['images']['name'][$i];
$sql = "INSERT INTO myimages (filename) VALUES ('".$filename."')";
$db->query($sql);
move_uploaded_file($_FILES['images']['tmp_name'][$i],'upload/'.$filename);
}
}
?>

Related

.Net Core v3.1 POSTing a large JSON object to a Razor Page handler

I have the following JavaScript:
var ids = [
"ed4bbe0e-d318-e811-a95f-000d3a11f5ee",
"386c9468-d11b-e811-a95d-000d3a11ec14",
"2913f317-991d-e811-a95d-000d3a11ec14"
];
$.ajax({
method: 'POST',
url: `/Jobs?handler=CreateJobsInQBO`,
dataType: 'json',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
// contentType: 'application/json',
data: { jobIds: ids }
})
POSTing to the Razor Page handler below
public async Task<JsonResult> OnPostCreateJobsInQBO(IEnumerable<string> jobIds) {
Result<List<Job>> result;
if (jobIds == null || !jobIds.Any()) {
result = "No jobs were submitted.";
} else {
result = await DoStuffAsync(jobIds);
}
return new JsonResult(result);
}
This works without issue.
The problem is that when my JavaScript array is large, roughly 3000 items, I get null for the incoming handler parameter value. I've tried setting the ajax contentType to application/json and also using JSON.stringify on the object before it's sent, but they don't seem to make a difference. I also tried setting [FormBody] on the parameter in the handler function, but again, it didn't work.
I know I'm missing something simple here. I just need a way to POST an array of string to a handler method that has roughly around 5000 items. Preferably without using the application/x-www-form-urlencoded content type as I think that is what's causing the large array to not work.
The default value of ValueCountLimit is 1024. It is a limit for the number of form entries to allow.
If the value count exceed 1024, you should increase it in Startup.cs
services.Configure<FormOptions>(options =>
{
options.ValueCountLimit = int.MaxValue;
});

Is it possible to send a list of files and a whole json object in a FormData object to a ASP.NET CORE MVC Controller

Just like the title says, how do I send a formdata object to a mvc controller with both a json object (including nested objects) and list of files.
I have already tried to stringify the object to a json object but the controller can not read the property, it reads the file list without problems.
Here is the controller method:
[HttpPost]
public IActionResult CreateTask(Task task, IEnumerable<IFormFile> files)
{
//some code
}
here is my javascript:
function createTask() {
var formData = new FormData();
var files = //some file objects
var obj = {
//some parameters
};
var task = JSON.stringify(task);
formData.append("task", task);
formData.append("files", files);
console.log(task);
$.ajax({
type: "POST",
url: "/Task/CreateTask",
processData: false,
contentType: false,
data: formData,
success: function (data) {
},
error: function (data) {
}
})
}
I need the controller method to read both the task and the file list at the same time if this is possible.
The only way to do this would be to bind the JSON sent as task to a string server-side. Then, you'd have to manually deserialize it into an object. In case it's not obvious, that also means you won't get any validation on any of the members of that JSON object. It will just be a string as far as ASP.NET Core and the modelbinder is concerned.
That said, I think the issue here is that you're needing to upload files and think that that necessitates posting as multipart/form-data. You can actually post as JSON and yet still include file uploads. That requires two changes, though:
You must bind the file "uploads" to byte[]s, instead of IFormFiles, server-side.
Client-side, you must add them to the JSON object you're posting as either Base64-encoded strings or uint8 arrays.
The first part is relatively straight-forward. The JSON deserializer invoked by the modelbinder will automatically convert Base64-encoded strings to byte array, and of course a JS unint8 array is essentially just a byte array, anyways.
The second part probably bears a bit more discussion. You'll need to need to use the File API to read the upload file data, and then convert that into either a Base64-encoded string or uint8 array:
Base64
var reader = new FileReader();
reader.onload = function(e) {
let base64 = btoa(reader.result);
myJsonObject.files.push(base64);
}
reader.readAsBinaryString(file);
Byte Array
var reader = new FileReader();
reader.onload = function(e) {
let bytes = Array.from(new Uint8Array(reader.result));
myJsonObject.files.push(bytes);
}
reader.readAsArrayBuffer(file);
You could try to convert object to form-data like below:
View
<script type="text/javascript">
$(document).ready(function () {
$("input").change(function () {
var formData = new FormData();
var files = $("#files")[0].files;
var obj = {
id: 1,
name: "jack"
};
for (var key in obj) {
formData.append(key, obj[key]);
}
for (var key in files) {
formData.append("files", files[key]);
}
$.ajax({
type: "POST",
url: "/api/values/CreateTask",
processData: false,
contentType: false,
data: formData,
success: function (data) {
},
error: function (data) {
}
})
});
});
</script>
Controller
[HttpPost]
public IActionResult CreateTask([FromForm]Task task, [FromForm]IEnumerable<IFormFile> files)
{
return Ok("Success");
}

How to send a serialized array to a php document?

I've got a JavaScript array named seat with many values in it.
As follows,I've serialized to be sent to a php file named confirm.php.
$('btnsubmit').click(function() {
var seat = [];
var seatar = JSON.stringify(seat);
$.ajax({
method: "POST",
url: "confirm.php",
data: seatar
})
})
And this is the code in my php file.
$seatarr = "";
if(isset($_POST['data']))
{
$seatarr = $_POST["data"];
print_r($seatarr);
}
I've tried with my level best, looking at the previous questions asked on this section. But no matter how hard I try to fix it, this code never works. Why not?
You're just sending raw JSON, but you're expecting to get a URI-encoded form with a data field in it. You can get that if you change your ajax call to use data: {data: seatar}:
$('btnsubmit').click(function() {
var seat = [];
var seatar = JSON.stringify(seat);
$.ajax({
method: "POST",
url: "confirm.php",
data: {data: seatar}
})
});
jQuery will automatically turn that object into a URI-encoded submission with a single field in it.
Then use json_decode if you want to parse it on the server:
if(isset($_POST['data']))
{
$seatarr = json_decode($_POST["data"]);
print_r($seatarr);
}
Also, as Mr. Blue pointed out, your button selector is probably incorrect. You have $('btnsubmit') which is looking for a <btnsubmit>...</btnsubmit> element. You probably meant $("#btnsubmit") (if you have id="btnsubmit" on the button) or $("[name=btnsubmit]") (if you have name="btnsubmit" on the button).
Another solution can be to rewrite the data like this:
seatar = $(this).serialize() + "&" + $.param(seatar);
and decode like T.J Crowder did propose:
if(isset($_POST['data']))
{
$seatarr = json_decode($_POST["data"]);
print_r($seatarr);
}

angular get image data and again append it to FormData

In angular 5 I am getting the images for hotelgallery from mongodb through my service. So basically the data what I am getting is like this
{
fieldname: "hotelgallery",
originalname: "e.jpg",
encoding: "7bit",
mimetype: "image/jpeg",
destination: "./public/",
encoding : "7bit",
filename : "1521139307413.jpg"
mimetype : "image/jpeg"
path : "public/1521139307413.jpg"
size : 66474
}
{
fieldname: "hotelgallery",
originalname: "e.jpg",
encoding: "7bit",
mimetype: "image/jpeg",
destination: "./public/",
encoding : "7bit",
filename : "1521139307413.jpg"
mimetype : "image/jpeg"
path : "public/1521139307413.jpg"
size : 66474
}
{
fieldname: "hotelgallery",
originalname: "j.jpg",
encoding: "7bit",
mimetype: "image/jpeg",
destination: "./public/",
encoding : "7bit",
filename : "1526753678390.jpg"
mimetype : "image/jpeg"
path : "public/1526753678390.jpg"
size : 66470
}
{
fieldname: "hotelgallery",
originalname: "k.jpg",
encoding: "7bit",
mimetype: "image/jpeg",
destination: "./public/",
encoding : "7bit",
filename : "7865456789413.jpg"
mimetype : "image/jpeg"
path : "public/7865456789413.jpg"
size : 66300
}
Now I want to again append those data to FormData but its not working.
The code what I have done so far
export class HotelEditComponent implements OnInit {
formData = new FormData();
ngOnInit() {
this.getOneHotel(this.route.snapshot.params['id']);
}
getOneHotel(id) {
this.http.get( this.apiUrl + '/api/hotel/' + id).subscribe(data => {
this.hotel = data;
this.appendImages(data['hotelimages']); //Here I am getting the data as mentioned here
});
}
public appendImages(imagedata) {
for (var i = 0; i < imagedata.length; i++) {
console.log(imagedata[i]);
this.formData.append('hotelgallery', imagedata[i], imagedata[i]['originalname']);
}
console.log(this.formData);
}
}
So can someone tell me how can I append the existing image data to FormData? Any help and suggestions will be really appreciable.
UseCase for this:
Actually I had used formData to upload images in angular. Now in the edit page the images are showing fine. But lets say a user edits some data and upload some images or remove some images. In that case I am getting the images from the database and again trying to upload them with formdata.
I have used this module and multer for nodejs to upload images with formData.
So can someone tell me how can I append the existing image data to FormData? Any help and suggestions will be really appreciable.
Actually, this approach need more add script solution. for example
1. Get Image Blob from server
Since you return detail object of images, not with the blob. You need have a endpoint to return as blob. (or if return as data buffer then it transform to blob you can use BlobUtil)
2. Put Blob to append form data
You need use blob to append in param 2 no a path, see documentation.
name
The name of the field whose data is contained in value.
value
The field's value. This can be a USVString or Blob (including subclasses
such as File).
filename Optional
The filename reported to the server
(a USVString), when a Blob or File is passed as the second parameter.
The default filename for Blob objects is "blob". The default filename
for File objects is the file's filename.
That what you need, but that is bad practice.
Let's say, you have 30 images to edit, then you need request blob endpoint to get those images blob to appends. But user only want to update 1 image, wasting time to request image blob, right?
For edit image usually we don't need append to file form (<input type="file" />).
Just show it as thumbnail to see what image uploaded and let file form empty.
What we do usually, thumbnail that image.
When user what to change, user put new image and replace old image with new want and update database.
if not, do nothing for image. (YAGNI)
FormData's append is silently failing here. You need to attach the 'image' as a blob. See the MDN docs.
formData.append('hotelgallery', new Blob([imagedata[i]], { type: "text/xml"}), imagedata[i]['originalname']);
Also, just printing formData won't show anything, instead try:
console.log(this.formData.getAll('hotelgallery'));
or
for (var value of this.formData.values()) {
console.log(value);
}
But lets say a user edits some data and upload some images or remove
some images. In that case I am getting the images from the database
and again trying to upload them with formdata.
So, you can pass object to universal method and on result get formData. Object data even can contain nested objects.
static createFormData(object: Object, form?: FormData, namespace?: string): FormData {
const formData = form || new FormData();
for (let property in object) {
if (!object.hasOwnProperty(property) || object[property] === undefined) {
continue;
}
const formKey = namespace ? `${namespace}[${property}]` : property;
if (object[property] instanceof Date) {
formData.append(formKey, object[property].toISOString());
} else if (typeof object[property] === 'object' && !(object[property] instanceof File)) {
this.createFormData(object[property], formData, formKey);
}
else if (typeof object[property] === 'number') {
let numberStr = object[property].toString().replace('.', ',');
formData.append(formKey, numberStr);
}
else {
formData.append(formKey, object[property]);
}
}
return formData;
}
}
In Component class:
export class HotelEditComponent implements OnInit {
formData = new FormData();
hotel: any;
...
ngOnInit() {
this.getOneHotel(this.route.snapshot.params['id']);
}
getOneHotel(id) {
this.http.get( this.apiUrl + '/api/hotel/' + id).subscribe(data => {
this.hotel = data;
});
}
postToServer() {
// or even can pass full `this.hotel` object
// this.helperService.createFormData(this.hotel)
this.formData = this.helperService.createFormData(this.hotel['hotelimages'])
this.service.post(this.formData);
}
...
}
It looks like you are trying to append a JSON array, since formData.append can only accept a string or blob, try the JSON.stringify() method to convert your array into a JSON string. (untested)
e.g. I think you can replace
this.appendImages(data['hotelimages']);
with
this.formData.append('hotelgallery', JSON.stringify(data['hotelimages']));
This is more of a design issue right now, rather then a tech problem. You are asking about posting FormData again and you want to fetch the images data again for that.
Now let's look at your current design.
User uploads 3 images of 4MB size each
On your edit page you downloads each of these images. Cost=12MB
On the edit page user deletes 2 images and adds 2 images. Cost=12MB
So final cost of updating 2 images of 8MB is 24MB. Which is a lot
Now before figuring out how to do FormData, figure out the right design for your app.
Consider the OLX site which allows you to post ads and later edit them. When you edit and remove a image, they call a API for removing the image
The ideal design in my opinion would be below
Submit all images in your create
For edit create a add and remove endpoint for the image
Submitting text data again on a edit form is ok, but submitting the same images data again on a edit form is never ok. Reconsider your design

jQuery using $.post to upload file

I need to send POST data by jQuery to my PHP server. And this is what I'm currently receiving (which is what I want):
client_id: 55
client_name: 'Josh'
age: '35'
extra: array(0) -> array(['preview'] -> true, ['lookup'] -> false)
And to achieve this I have the following code:
var elements = [];
var listOfExtra = []
listOfExtra.push({'preview': true, 'lookup': false});
elements['client_id'] = 55;
elements['client_name'] = 'Josh';
elements['age'] = 35;
elements['extra'] = listOfExtra;
$.post(url, $.extend({}, elements));
But now I also need to send a file the user has upload, so by doing this:
elements['file'] = event.target.files[0];
I receive the message Illegal invocation in javascript
Since this didn't work I tried to implement a formData():
var formData = new FormData();
formData.append('client_id', 55);
formData.append('client_name', 'Josh');
formData.append('age', 35);
formData.append('extra', listOfExtra);
formData.append('file', event.target.files[0]);
$.ajax(
{
url: url
type: 'POST',
data: formData,
processData: false,
contentType: false
});
What happens is that now the extra is an [object][object], which is OK, so I just need to stringify it.
formData.append('extra', JSON.stringify(listOfExtra));
The problem now is that I have lots of code that uses $_POST['extra'][0]['preview'] (for example) and now I need to decode all of it before and use lots of conditions, like:
$extra = isset(json_decode($_POST['extra'])[0]['preview']);
if($extra != null)
$extraContent = json_decode($_POST['extra'])[0];
$preview = (isset($extraContent) ? $extraContent->preview : $extra[$_POST['extra'][0]['preview']);
Is there any way by using formData() or $.post I can keep the values sent from javascript to PHP like I want and send the file?
An easy way is, to not use jQuery for that but simply
var xhr = new XMLHttpRequest();
xhr.send(formData);
It should do all the right things, but works only in modern browsers (don't know if you need the older ones too). For compatibility look at http://caniuse.com/#feat=xhr2
Solved.
I converted the JSON values decoded into a normal $_POST again.
$extraContent = json_decode($_POST['extra'])[0];
foreach($extraContent as $key => $extra)
$_POST[$key] = $extra;

Categories