Send Multiple Files to PHP with Jquery and Fetch - javascript

I'm building an HTML form using Jquery to send the data to a PHP backend. It is supposed to allow for multiple attachments, which are then sent via fetch. I'm receiving the $_POST fine, but $_FILES only contains the last file selected.
I know this is because the multiple attribute resets every time a new file picker window is opened, but I it to save each file as it is selected. To address this, I'm trying to capture each file in a JS array, appending the array to FormData before it posts to the server.
$(document).ready(function(e){
// Add input files to array
$('#inputId').on('change', function(e) {
fileList.push(fileName[0]);
});
// Form submission
$("#formId").on('submit', function(e){
e.preventDefault();
formData = new FormData(this);
formData.append(fileList);
url = '../php/submit.php';
fetch(url, {method: 'POST', body: formData})
.then(function(response) {
return response.text();
})
.then(function(body){
console.log(body);
});
});
});
<form id="formId" enctype="multipart/form-data" method="POST">
<div class="ds-row ds-file-upload-container ds-mar-b-2">
<div class="ds-col-xs-12 ds-col-md-6">
<label for="inputId" class="ds-button">Choose a file
<input type="file" class="ds-file-upload" name="upload[]" id="inputId" multiple="multiple">
</label>
</div>
</div>
</form>
As of now, the form is not submitting anything when the submit button is clicked. If I comment out the #inputId onchange event, it works, but of course, it doesn't have all the files included in the FormData.

Here is working code for you and tested as well.
You have few issues with formData append. You are not doing any forEach for your file so so when you are sending the fetch request you formData only gets the last file.
To store multiple file you need to have an empty array which i have named as filesToUpload
This array will store all the file which you will select on change function.
Once the array contains all the files you want to upload. On form submit function you will loop through that filesToUpload array and append all its data to formData and send it via fetch request.
On the backend PHP side when you var_dump($_POST) OR var_dump($_FILES) you will see all these files there.
Working fiddle Demo: https://jsfiddle.net/woft6grd/
In the demo when you go dev tools -> network you will see on form submit your request will have all the files you uploaded via input.
jQuery
$(document).ready(function() {
// Add input files to array
var filesToUpload = []
//On Change loop through all file and push to array[]
$('#inputId').on('change', function(e) {
for (var i = 0; i < this.files.length; i++) {
filesToUpload.push(this.files[i]);
}
});
// Form submission
$("#formId").on('submit', function(e) {
e.preventDefault();
//Store form Data
var formData = new FormData()
//Loop through array of file and append form Data
for (var i = 0; i < filesToUpload.length; i++) {
var file = filesToUpload[i]
formData.append("uploaded_files[]", file);
}
//Fetch Request
url = '../php/submit.php';
fetch(url, {
method: 'POST',
body: formData
})
.then(function(response) {
return response.text();
})
.then(function(body) {
console.log(body);
});
})
});
HTML
<form id="formId" enctype="multipart/form-data" method="POST">
<div class="ds-row ds-file-upload-container ds-mar-b-2">
<div class="ds-col-xs-12 ds-col-md-6">
<label for="inputId" class="ds-button">Choose a file
<input type="file" class="ds-file-upload" name="upload[]" id="inputId" multiple="multiple" required>
</label>
</div>
</div>
<input type="submit" value="Upload" />
</form>
I have added comments to each code line as well to highlight as well.

Related

Adding a file option to the present form field

I have a feedback form in my website. Its very basic and only having a text area to put user feedback and submit.
now i am planing to add one option for attaching a picture along with feedback. Adding another text field easy but
i can't figure out how can i add a file into the JavaScript. Please suggest the required changes to add a file into the below script
function upload() {
var feedback = _("feedback").value;
var status = _("status");
if (feedback == "") {
status.innerHTML = "Empty";
} else {
status.innerHTML = 'please wait ...';
var ajax = ajaxObj("POST", "feedback.php");
ajax.onreadystatechange = function() {
if (ajaxReturn(ajax) == true) {
if (ajax.responseText != "ok") {
status.innerHTML = ajax.responseText;
} else {
window.location = "thanks.php";
}
}
}
ajax.send("feedback=" + feedback);
}
}
<form id="form1" enctype="multipart/form-data" onsubmit="return false;">
<input id="feedback" type="text">
<button id="submit" onclick="upload()">Submit Details</button>
<span id="status"></span>
</form>
Here you are:
var x = document.createElement("input");
x.setAttribute("type", "file");
document.querySelector("#form1").appendChild(x);
Hope this help.
Unless you're trying to upload the file using ajax, just submit the form to feedback.php.
<form enctype="multipart/form-data" action="feedback.php" method="post">
<input id="image-file" type="file" />
</form>
If you want to upload the image in the background (e.g. without submitting the whole form) you'll need to use Flash since JavaScript alone can't do this.
jQuery Ajax File Upload
Ajax using file upload
jquery easy example look at first answer
Okay.. so two things you will have t change:
Remove that header ('application/x-www-form-urlencoded') and add 'multipart/form-data' header instead. files can not be sent as urlencoded.
Secondly, in ajax request, instead of sending feedback as string, you need to send FormData object, which supports file upload over ajax:
var myForm = $("#form1")[0];
var formData = new FormData(myForm);
ajax.send(formData);
Update:
Forgot to mention: Of course the third thing you will need is to add to your form!

Angular file upload happens immediately onchange

I've had a little bit of a rough go on uploading files through angular and have settled on the following solution:
$scope.uploadFile = function(files) {
var fd = new FormData();
//Take the first selected file
fd.append("file", files[0]);
console.log($scope.files);
$http.post('../resource/editor', fd, {
withCredentials: true,
headers: {'Content-Type': undefined },
transformRequest: angular.identity
}).then(function(data){
//this keeps the user from having to refresh the page
DataContext.getEditors().then(function(data){
$scope.myData = data;
});
}).catch(function(reason){
});
};
<form name="myForm" >
<input type="file" name="file" onchange="angular.element(this).scope().uploadFile(this.files)"/>
<input type="submit" ng-click="uploadFile()" value='Submit'/>
</form>
The current behavior is that as soon as the user selects a file, that file begins to upload. My intent is that on the click event of the button that user will upload the file. What am I missing to accomplish this?
It looks like you're calling uploadFile() when your file input changes. That's why it's happening immediately. If you need to know what files are selected, add a statement to update some model data onchange:
<input type="file" name="file" onchange="$scope.files = this.files"/>
Then change your uploadFile() method so that it uses the $scope.files variable:
$scope.uploadFile = function() {
...
//Take the first selected file
fd.append("file", $scope.files[0]);
...
};
Hope this helps!
You are calling upload file on the change of the input I would give the input a modal and pass that value to the upload file of the button click.
<form name="myForm" >
<input type="file" name="file" ng-model="fileNames" />
<input type="submit" ng-click="uploadFile(fileName)" value='Submit'/>
</form>

Integrating Dropzone.js into existing HTML form with other fields

I currently have a HTML form which users fill in details of an advert they wish to post. I now want to be able to add a dropzone for uploading images of the item for sale.
I have found Dropzone.js which seems to do most of what I need. However, when looking into the documentation, it appears that you need to specify the class of the whole form as dropzone (as opposed to just the input element). This then means that my entire form becomes the dropzone.
Is it possible to use the dropzone in just part of my form, i.e. by only specifying the element as class "dropzone", rather than the whole form?
I could use separate forms, but I want the user to be able to submit it all with one button.
Alternatively, is there another library that can do this?
Many thanks
Here's another way to do it: add a div in your form with a classname dropzone, and implement dropzone programmatically.
HTML :
<div id="dZUpload" class="dropzone">
<div class="dz-default dz-message"></div>
</div>
JQuery:
$(document).ready(function () {
Dropzone.autoDiscover = false;
$("#dZUpload").dropzone({
url: "hn_SimpeFileUploader.ashx",
addRemoveLinks: true,
success: function (file, response) {
var imgName = response;
file.previewElement.classList.add("dz-success");
console.log("Successfully uploaded :" + imgName);
},
error: function (file, response) {
file.previewElement.classList.add("dz-error");
}
});
});
Note : Disabling autoDiscover, otherwise Dropzone will try to attach twice
I had the exact same problem and found that Varan Sinayee's answer was the only one that actually solved the original question. That answer can be simplified though, so here's a simpler version.
The steps are:
Create a normal form (don't forget the method and enctype args since this is not handled by dropzone anymore).
Put a div inside with the class="dropzone" (that's how Dropzone attaches to it) and id="yourDropzoneName" (used to change the options).
Set Dropzone's options, to set the url where the form and files will be posted, deactivate autoProcessQueue (so it only happens when user presses 'submit') and allow multiple uploads (if you need it).
Set the init function to use Dropzone instead of the default behavior when the submit button is clicked.
Still in the init function, use the "sendingmultiple" event handler to send the form data along wih the files.
Voilà ! You can now retrieve the data like you would with a normal form, in $_POST and $_FILES (in the example this would happen in upload.php)
HTML
<form action="upload.php" enctype="multipart/form-data" method="POST">
<input type="text" id ="firstname" name ="firstname" />
<input type="text" id ="lastname" name ="lastname" />
<div class="dropzone" id="myDropzone"></div>
<button type="submit" id="submit-all"> upload </button>
</form>
JS
Dropzone.options.myDropzone= {
url: 'upload.php',
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 5,
maxFiles: 5,
maxFilesize: 1,
acceptedFiles: 'image/*',
addRemoveLinks: true,
init: function() {
dzClosure = this; // Makes sure that 'this' is understood inside the functions below.
// for Dropzone to process the queue (instead of default form behavior):
document.getElementById("submit-all").addEventListener("click", function(e) {
// Make sure that the form isn't actually being sent.
e.preventDefault();
e.stopPropagation();
dzClosure.processQueue();
});
//send all the form data along with the files:
this.on("sendingmultiple", function(data, xhr, formData) {
formData.append("firstname", jQuery("#firstname").val());
formData.append("lastname", jQuery("#lastname").val());
});
}
}
The "dropzone.js" is the most common library for uploading images.
If you want to have the "dropzone.js" as just part of your form, you should do the following steps:
1) for the client side:
HTML :
<form action="/" enctype="multipart/form-data" method="POST">
<input type="text" id ="Username" name ="Username" />
<div class="dropzone" id="my-dropzone" name="mainFileUploader">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
</div>
</form>
<div>
<button type="submit" id="submit-all"> upload </button>
</div>
JQuery:
<script>
Dropzone.options.myDropzone = {
url: "/Account/Create",
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
maxFiles: 100,
acceptedFiles: "image/*",
init: function () {
var submitButton = document.querySelector("#submit-all");
var wrapperThis = this;
submitButton.addEventListener("click", function () {
wrapperThis.processQueue();
});
this.on("addedfile", function (file) {
// Create the remove button
var removeButton = Dropzone.createElement("<button class='btn btn-lg dark'>Remove File</button>");
// Listen to the click event
removeButton.addEventListener("click", function (e) {
// Make sure the button click doesn't submit the form:
e.preventDefault();
e.stopPropagation();
// Remove the file preview.
wrapperThis.removeFile(file);
// If you want to the delete the file on the server as well,
// you can do the AJAX request here.
});
// Add the button to the file preview element.
file.previewElement.appendChild(removeButton);
});
this.on('sendingmultiple', function (data, xhr, formData) {
formData.append("Username", $("#Username").val());
});
}
};
</script>
2) for the server side:
ASP.Net MVC
[HttpPost]
public ActionResult Create()
{
var postedUsername = Request.Form["Username"].ToString();
foreach (var imageFile in Request.Files)
{
}
return Json(new { status = true, Message = "Account created." });
}
I have a more automated solution for this.
HTML:
<form role="form" enctype="multipart/form-data" action="{{ $url }}" method="{{ $method }}">
{{ csrf_field() }}
<!-- You can add extra form fields here -->
<input hidden id="file" name="file"/>
<!-- You can add extra form fields here -->
<div class="dropzone dropzone-file-area" id="fileUpload">
<div class="dz-default dz-message">
<h3 class="sbold">Drop files here to upload</h3>
<span>You can also click to open file browser</span>
</div>
</div>
<!-- You can add extra form fields here -->
<button type="submit">Submit</button>
</form>
JavaScript:
Dropzone.options.fileUpload = {
url: 'blackHole.php',
addRemoveLinks: true,
accept: function(file) {
let fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onloadend = function() {
let content = fileReader.result;
$('#file').val(content);
file.previewElement.classList.add("dz-success");
}
file.previewElement.classList.add("dz-complete");
}
}
Laravel:
// Get file content
$file = base64_decode(request('file'));
No need to disable DropZone Discovery and the normal form submit will be able to send the file with any other form fields through standard form serialization.
This mechanism stores the file contents as base64 string in the hidden input field when it gets processed. You can decode it back to binary string in PHP through the standard base64_decode() method.
I don't know whether this method will get compromised with large files but it works with ~40MB files.
Enyo's tutorial is excellent.
I found that the sample script in the tutorial worked well for a button embedded in the dropzone (i.e., the form element). If you wish to have the button outside the form element, I was able to accomplish it using a click event:
First, the HTML:
<form id="my-awesome-dropzone" action="/upload" class="dropzone">
<div class="dropzone-previews"></div>
<div class="fallback"> <!-- this is the fallback if JS isn't working -->
<input name="file" type="file" multiple />
</div>
</form>
<button type="submit" id="submit-all" class="btn btn-primary btn-xs">Upload the file</button>
Then, the script tag....
Dropzone.options.myAwesomeDropzone = { // The camelized version of the ID of the form element
// The configuration we've talked about above
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 25,
maxFiles: 25,
// The setting up of the dropzone
init: function() {
var myDropzone = this;
// Here's the change from enyo's tutorial...
$("#submit-all").click(function (e) {
e.preventDefault();
e.stopPropagation();
myDropzone.processQueue();
});
}
}
Further to what sqram was saying, Dropzone has an additional undocumented option, "hiddenInputContainer". All you have to do is set this option to the selector of the form you want the hidden file field to be appended to. And voila! The ".dz-hidden-input" file field that Dropzone normally adds to the body magically moves into your form. No altering the Dropzone source code.
Now while this works to move the Dropzone file field into your form, the field has no name. So you will need to add:
_this.hiddenFileInput.setAttribute("name", "field_name[]");
to dropzone.js after this line:
_this.hiddenFileInput = document.createElement("input");
around line 547.
I want to contribute an answer here as I too have faced the same issue - we want the $_FILES element available as part of the same post as another form. My answer is based on #mrtnmgs however notes the comments added to that question.
Firstly: Dropzone posts its data via ajax
Just because you use the formData.append option still means that you must tackle the UX actions - i.e. this all happens behind the scenes and isn't a typical form post. Data is posted to your url parameter.
Secondly: If you therefore want to mimic a form post you will need to store the posted data
This requires server side code to store your $_POST or $_FILES in a session which is available to the user on another page load as the user will not go to the page where the posted data is received.
Thirdly: You need to redirect the user to the page where this data is actioned
Now you have posted your data, stored it in a session, you need to display/action it for the user in an additional page. You need to send the user to that page as well.
So for my example:
[Dropzone code: Uses Jquery]
$('#dropArea').dropzone({
url: base_url+'admin/saveProject',
maxFiles: 1,
uploadMultiple: false,
autoProcessQueue:false,
addRemoveLinks: true,
init: function(){
dzClosure = this;
$('#projectActionBtn').on('click',function(e) {
dzClosure.processQueue(); /* My button isn't a submit */
});
// My project only has 1 file hence not sendingmultiple
dzClosure.on('sending', function(data, xhr, formData) {
$('#add_user input[type="text"],#add_user textarea').each(function(){
formData.append($(this).attr('name'),$(this).val());
})
});
dzClosure.on('complete',function(){
window.location.href = base_url+'admin/saveProject';
})
},
});
You can modify the formData by catching the 'sending' event from your dropzone.
dropZone.on('sending', function(data, xhr, formData){
formData.append('fieldname', 'value');
});
In order to submit all files alongside with other form data in a single request you can copy Dropzone.js temporary hidden input nodes into your form. You can do this within addedfiles event handler:
var myDropzone = new Dropzone("myDivSelector", { url: "#", autoProcessQueue: false });
myDropzone.on("addedfiles", () => {
// Input node with selected files. It will be removed from document shortly in order to
// give user ability to choose another set of files.
var usedInput = myDropzone.hiddenFileInput;
// Append it to form after stack become empty, because if you append it earlier
// it will be removed from its parent node by Dropzone.js.
setTimeout(() => {
// myForm - is form node that you want to submit.
myForm.appendChild(usedInput);
// Set some unique name in order to submit data.
usedInput.name = "foo";
}, 0);
});
Obviosly this is a workaround dependent on implementation details. Related source code.
Working solution for 5.7.0 version
<form id="upload" enctype="multipart/form-data">
<input type="text" name="name" value="somename">
<input type="checkbox" name="terms_agreed">
<div id="previewsContainer" class="dropzone">
<div class="dz-default dz-message">
<button class="dz-button" type="button">
Drop files here to upload
</button>
</div>
</div>
<input id="dz-submit" type="submit" value="submit">
</form>
Dropzone.autoDiscover = false;
new Dropzone("#upload",{
clickable: ".dropzone",
url: "upload.php",
previewsContainer: "#previewsContainer",
uploadMultiple: true,
autoProcessQueue: false,
init() {
var myDropzone = this;
this.element.querySelector("[type=submit]").addEventListener("click", function(e){
e.preventDefault();
e.stopPropagation();
myDropzone.processQueue();
});
}
});
Here is my sample, is based on Django + Dropzone. View has select(required) and submit.
<form action="/share/upload/" class="dropzone" id="uploadDropzone">
{% csrf_token %}
<select id="warehouse" required>
<option value="">Select a warehouse</option>
{% for warehouse in warehouses %}
<option value={{forloop.counter0}}>{{warehouse.warehousename}}</option>
{% endfor %}
</select>
<button id="submit-upload btn" type="submit">upload</button>
</form>
<script src="{% static '/js/libs/dropzone/dropzone.js' %}"></script>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script>
var filename = "";
Dropzone.options.uploadDropzone = {
paramName: "file", // The name that will be used to transfer the file,
maxFilesize: 250, // MB
autoProcessQueue: false,
accept: function(file, done) {
console.log(file.name);
filename = file.name;
done(); // !Very important
},
init: function() {
var myDropzone = this,
submitButton = document.querySelector("[type=submit]");
submitButton.addEventListener('click', function(e) {
var isValid = document.querySelector('#warehouse').reportValidity();
e.preventDefault();
e.stopPropagation();
if (isValid)
myDropzone.processQueue();
});
this.on('sendingmultiple', function(data, xhr, formData) {
formData.append("warehouse", jQuery("#warehouse option:selected").val());
});
}
};
</script>
This is just another example of how you can use Dropzone.js in an existing form.
dropzone.js :
init: function() {
this.on("success", function(file, responseText) {
//alert("HELLO ?" + responseText);
mylittlefix(responseText);
});
return noop;
},
Then, later in the file I put
function mylittlefix(responseText) {
$('#botofform').append('<input type="hidden" name="files[]" value="'+ responseText +'">');
}
This assumes you have a div with id #botofform that way when uploading you can use the uploaded files' names.
Note: my upload script returned theuploadedfilename.jpeg
dubblenote you also would need to make a cleanup script that checks the upload directory for files not in use and deletes them ..if in a front end non authenticated form :)
Try this
<div class="dropzone dz-clickable" id="myDrop">
<div class="dz-default dz-message" data-dz-message="">
<span>Drop files here to upload</span>
</div>
</div>
JS
<script>
Dropzone.autoDiscover = false;
Dropzone.default.autoDiscover=false;
$("div#myDrop").dropzone({
url: "/file/post",
});
</script>

How to submit a form with specific fieldset

I have a form like this:
<form name="paymentForm" id="paymentForm" action="/submit.jsp" method="post">
<fieldset id="ccData">
<input id="ccNumber" name="ccNumber"/>
</fieldset>
<fieldset id="otherData">
<input id="requestId" name="requestId"/>
</fieldset>
</form>
When you slick submit, I would like to submit(via ajax) only #ccData filedset to some different url (e.g. submitCC.jsp) and based on response I want to submit full form to actual url.
How can I achieve that ?
Use jQuery's serialize method
var formData = $("#ccData").serialize()​;
$.post("TheUrl",formData);
You could do that with JavaScript - e.g jQuery. You build an eventHandler like
$('#paymentForm').on('click', function () {
$(this).preventDefault();
if ($(this).hasClass('first_send')) {
$.ajax({
url: "your_url",
data: { ccData: $('#ccData').val()}
}).done(function ( data ) {
$('#paymentForm').addClass('first_send')
// examin the data, insert stuff you need and send the form again
// with ajax
})
} else {
$(this).removeClass('first_send')
// this is the second send - so do stuff here - show a result or so
}
})
With the class first_send you can check if it is the first send or the second. This is just an untested, incomplete idea how you could do it. I guess you get the big picture ...

Handling Json results in ASP.NET MVC with jQuery

I am working on a view containing two forms, handled by separate controller actions which return a string serilaized to Json:
return Json(message);
The forms are submitted using jQuery, by clicking on a button outside the two forms.
The button handler:
$('#inviteForm').ajaxSubmit({
success: function(html, status) {
$("#response").text(html);
}
})
$('#trialForm').ajaxSubmit({
success: function(html, status) {
$("#response").append(html);
}
});
The browser receives the result and prompts the user to download as it is interpreted as "application/json".
However, if I only submit one of these forms in the jQuery, the resulting Json message is displayed as a string in the #response element as desired.
Why does adding a second ajaxSubmit() cause this different behaviour?
Thanks.
The view contains the following forms:
<form action="/Controller1/SaveAttachments/<%=Model.ObjectId %>" id="trialForm" method="post" enctype="multipart/form-data">
<input type="file" name="trialForm" size=30/>
<input type="file" name="trialSheet" size=30/>
<input type="file" name="trialApproval" size=30/>
</form>
and...
<form action="/Controller1/UpdateTemplate/<%=Model.ObjectId %>" id="inviteForm" method="post" enctype="multipart/form-data">
<%=Html.TextArea("invitationSheet", Model.InvitationSheet,
new { #name = "invitationSheet"})
<script type="text/javascript">
window.onload = function() {
var sBasePath = '<%=Url.Content("~/Content/FCKeditor/")%>';
var oFCKeditor = new FCKeditor('invitationSheet');
oFCKeditor.BasePath = sBasePath;
oFCKeditor.HtmlEncodeOutput = true;
oFCKeditor.ReplaceTextarea();
}
</script>
</form>
Update:
You can't upload files directly via AJAX so it is doing an actual post of the form containing file inputs. You should look at a plugin that will let you upload files using the hidden iframe technique that works asynchronously instead of trying to upload using AJAX.

Categories

Resources