I'm using Ajax for a fileupload app that list the uploaded files without refreshing the page, the user can then download all the files uploaded as .zip.
The problem is that I'd like to send (POST method) an array with all the files path to my backend so they can be compressed as .zip and I have some issues storing them all the paths into one single array like this : array = ['path1', 'path2', 'path3']
Since the files are uploaded one by one here is what happens to my array on console.log() :
array = ['path1']
array = ['path2']
array = ['path3']
...
The backend expect all the files to be sent directly in one array in order to group them in a .zip file. How can I do it ?
Here's the ajax upload code :
$("#fileupload").fileupload({
dataType: 'json',
sequentialUploads: true, /* SEND THE FILES ONE BY ONE */
done: function (e, data) {
if (data.result.is_valid) {
mylist = [];
mylist.push(data.result.url);
console.log(mylist); /* HERE IS THE CONSOLE.LOG() */
$(document).on('submit', '#formDownload', function(e){
e.preventDefault();
if($(this).is('#formDownload')) {
$.ajax({
type:'POST',
url:'/tools/getfiles/',
data:{
path:mylist, /* array that'll be received by the backend*/
csrfmiddlewaretoken:$('#formDownload input[name=csrfmiddlewaretoken]').val(),
},
});
}
});
}
}
});
Is there any way to resolve this ?
Related
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);
}
}
?>
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");
}
i'm using Dropzonejs and what I would like to do is essentially simple. But I can't find any article about it.
So on sending the file we trigger an error on nasty .exe and .php files. The dropzonejs interface show an X and and error message. So that's correct. The problem is that it still gets thrown into the on success event, and gets uploaded.
uploader.on("sending", function (file, xhr, data) {
var aDeny = [ // deny file some extensions by default
'application/x-msdownload',
'text/php'
];
if($.inArray(file.type, aDeny) !== -1) {
this.defaultOptions.error(file, 'File not allowed: ' + file.name);
return false;
}
});
Evil.exe still appears in this success event and gets uploaded. The response only has a string of the file path and file.status is success.
uploader.on('success', function (file, response) {
getData({'dostuff'});
return file.previewElement.classList.add("dz-success");
});
So in my 'sending' event, how can I prevent the file from appearing in the success event?
UPDATE:
Thanks! This is what I needed in the end:
var aDeny = [ // deny file some extensions by default
'application/x-msdownload',
'text/php'
];
Dropzone.options.uploadWidget = {
// more config...
accept: function (file, done) {
if ($.inArray(file.type, aDeny) !== -1) {
done("This file is not accepted!");
}
else {
done();
}
}
}
I would first of all always check the file type on the server side to prevent any problems.
Then to filter file types with Dropzone you can use:
acceptedFiles option
The default implementation of accept checks the file's mime type or
extension against this list. This is a comma separated list of mime
types or file extensions.
Eg.: image/*,application/pdf,.psd
If the Dropzone is clickable this option will also be used as accept
parameter on the hidden file input as well.
Sample:
var myDropzone = new Dropzone("div#myId", {
url: "/file/post",
acceptedFiles: 'application/x-msdownload,text/php'
});
accept function
A function that gets a file and a done function as parameters.
If the done function is invoked without arguments, the file is
"accepted" and will be processed. If you pass an error message, the
file is rejected, and the error message will be displayed. This
function will not be called if the file is too big or doesn't match
the mime types.
Sample:
Dropzone.options.myAwesomeDropzone = {
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 2, // MB
accept: function(file, done) {
if (file.name == "justinbieber.jpg") {
done("This file is not accepted!");
}
else { done(); }
}
};
I am practicing dropzoneJS and I wanna know how to get the index of a certain file in the list because I want to delete an element of an array related to the file I uploaded.
I made an array called uploadedImages.
var uploadedImages = []; //I somehow made it to store the image array from database
I want to store to uploadImages the file names on addedfile function
myDropzone.on("addedfile", function(file) {
uploadedImages.push(file.name);
});
Then filter out the images from uploadedImages the files removed but I did it with the file.name of Dropzone but what if I have plenty of images with the same name or somehow I uploaded the same file multiple times. I think the better way is to find the index.
myDropzone.on("removedfile", function(file) {
var filterUpload = uploadedImages.filter(function(img_file){
return img_file != file.name;
});
uploadedImages = filterUpload;
});
Any suggestions please.
Thanks in advance guys.
Server Side
After successfull validation & upload at server side, store image info in the database. Then get imageID (last inserted ID in DB table) and store it in $_SESSION array:
$_SESSION["uploadedImages"][$imageID] = fileName;
At the end of the PHP code output a json response with File ID:
$out = array("fileID"=>$imageID);
echo json_encode($out);
Client Side
Use that file ID in Dropzone callback and store it in the File object like the following:
dropZone.on("success", function(file, response){
obj = JSON.parse(response);
//Save file id returned from server:
file.id = obj.fileID;
});
Now when removing a file, send the stored fileID to the server:
dropZone.on("removedfile", function(file){
//Delete uploaded file from server:
var imageID = file.id;
$.post("upload.php","deleteImage="+imageID, function(data){
alert("removed")
});
});
then simply in upload.php use the ID to delete the image from database and from $_SESSION array.
This partially answers your question. It will get the index from the array of files that Dropzone currently has.
myDropzone.on("removedfile", function(file) {
// get index of dropzone file
var index = myDropzone.files.map(function (obj, index) {
if (file == obj) {
return index;
}
}).filter(isFinite)[0];
});
You should make a variable storing files and get the index on removedfile callback like the following:
success: function(file, response) {
dz_uploaded_url.push(file);
},
removedfile: function(file) {
var index = dz_uploaded_url.map(function(d, index) {
if(d == file) return index;
}).filter(isFinite)[0];
var _ref;
return (_ref = file.previewElement) != null ? _ref.parentNode.removeChild(file.previewElement) : void 0;
}
I have a website, where users can get inbox messages and notifications while they are on the website. (Like on facebook, you see (1) at the begining of the tile as you have notification)
Currently I have an ajax request which grabs the data the title has to show. It works liek charm but the issue is that this file is called every 10 seconds. If user has 10 page tabs though, this file is called 10x10=100 times.. if my site has thousand users, you understand how much load it would generate.
I though of running the javascript on active tab only but how can I update the title of all opened tabs of my website? Any other suggestion?
Here is my code
var oldtitle=$(document).attr("title");
var checker=function(){
$.ajax({
url : 'live_title.php',
type : 'POST',
success : function(data) {
... code ....
... code ....
... code ....
if (sum>0) {
$(document).attr("title", "("+sum+") "+oldtitle);
}
}
});
}
setInterval(checker,20000);
checker();
A cache mechanism seems the right way to go.
First idea: use HTTP caching
Be sure to add a parameter as a query string with the current timestamp rounded to the previous 10th of second.
Be sure your web server sends the correct header for the HTTP cache to work. It's best with a GET request.
Example:
$.ajax({
url : 'live_title.php',
type : 'GET',
success : function(data) {
// code
},
data: {t: Math.floor((+new Date())/10000)}
}
// we send a request similar to live_title.php?t=142608488
Second idea: use window.localStorage as a secondary local cache.
Additionnaly to the first idea:
var getCache = function(t) {
if (window.localStorage) {
var liveTitle = localStorage.getItem('liveTitle') || {};
return liveTitle[t] || null;
}
};
var setCache = function(t, data) {
if (window.localStorage) {
window.localStorage.setItem('liveTitle', {t:data});
}
}
var run = function() {
var t = Math.floor((+new Date())/10000);
var cache = getCache(t);
var success = function(data) {
/*code*/
};
if (cache) {
success(cache);
}
else {
$.ajax({
url : 'live_title.php',
type : 'GET',
success : function(data) {
setCache(t, data);
success(data);
},
data: {t: t}
}
}
}
I don't think you can do what you want easily.
Moreover to optimize that, I would recommend to use cache :
One time a tab calls the method which count the messages, do the query and cache the result to a simple file or in memory
during the next 5 minutes, each time a tab calls the method, use the cache and do not query the database
when the 5 minutes are passed, do again a query, cache it and so on.
Like this, on 100 calls, you have only 1 big request, others are like requesting a js or img files