I searched the internet and found this JavaScript and jQuery template for a file upload progress bar that works 100% fine(given the fact that you only use one form input).
My situation is that I need to pass one file and 4 other inputs like text and select to a Controller Action. The action works fine. My problem is to pass all these values through ajax to the Action whilst maintaining the progress bar functionality.
Action Parameters
[HttpPost]
public ActionResult Add_Attachment_to_Process(int id, int Department_id, HttpPostedFileBase Attachment, string sel_checkTask, string cbx_checkTask = null)
HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<form method="post" enctype="multipart/form-data" action="/Processes/Add_Attachment_to_Process" id="myform">
<input type="file" id="media" name="file" />
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" aria-label="Checkbox for following text input" id="cbx_checkTask" name="cbx_checkTask">
<span id="span_checkTask">Link Task</span>
</div>
</div>
<select class="form-control" id="sel_checkTask" name="sel_checkTask" style="width : 700px;" disabled>
#foreach (var t in Model.User_Tasks)
{
<option value="#t.Task_Discription">#t.Task_Discription - #t.Key_Terms</option>
}
</select>
</div>
<input id="id" name="id" value="#ViewBag.process_id " />
<input id="Department_id" name="Department_id" value="#ViewBag.Department_id" />
<input type="submit" />
</form>
<div class="progress" style="width:40%">
<div id="uploadprogressbar" class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width:0%">
0%
</div>
</div>
JavaScript
$(document).ready(function () {
$("#myform").on('submit', function (event) {
event.preventDefault();
var formData = new FormData($("#myform")[0]);
$.ajax({
xhr: function () {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function (e) {
if (e.lengthComputable) {
console.log('Bytes Loaded: ' + e.loaded);
console.log('Total Size: ' + e.total);
console.log('Percentage Uploaded: ' + ((e.loaded / e.total) * 100) + '%');
var percent = Math.round((e.loaded / e.total) * 100);
$("#uploadprogressbar").html(percent + '%');
$("#uploadprogressbar").width(percent + '%');
}
});
return xhr;
},
type: 'POST',
url: '/Processes/Add_Attachment_to_Process',
data: formData,
processData: false,
contentType: false,
success: function () {
alert('File Uploaded');
},
error: function (xhr, status, error) {
var errorMessage = xhr.status + ': ' + xhr.statusText;
alert('Error - ' + errorMessage);
}
});
});
});
AS per the discussion above, try this sort of pattern to better see what values are not being sent
let f = new FormData();
f.append('id', getYouFormValue("id"));
f.append('sel_checkTask', getYouFormValue("sel_checkTask"));
f.append('cbx_checkTask ', getYouFormValue("cbx_checkTask "));
if (form.File) {
f.append('File', getYouFormValue("file"));
}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: f
}
return fetch(`/Processes/Add_Attachment_to_Process`, requestOptions)
.then(handleResponse)
.then(result => {
//do stuff
});
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
console.log('not logged in')
}
const error = (data && data.message) || data.title || response.statusText;
return Promise.reject(error);
}
return data;
});
}
function getYouFormValue(element){
let val = document.getElementById(element);
if(!val){
console.log('empty value');
return null;
}
return val;
}
Related
Upon ajax successful return, the return data contains status. if status is false(not ajax fail), return data contains array of element names and associated error message. The goal is to replicate .validate() error functionality by appending msg after element and highlight.
HTML form:
<form class="form-horizontal" id="chngPwdForm" action="changepwd.php" method="post">
.
. various HTML....
.
<div class="form-group">
<div class="alert alert-div font-weight-bold" id="errorTxt" role="alert" style="display:none;"> </div>
<label for="crntPwd">Existing Password:</label>
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-key fa-fw"></i></span>
<input type="password" class="form-control" id="crntPwd" name="crntPwd" required>
</div>
</div>
.
. more HTML follows
.
JQUERY:
.
.yada, yada, yada
.
$("#chngPwdForm").submit(function(event){
event.preventDefault();
if ($("#chngPwdForm").valid() ) {
$('#chngPwdForm div[id="errorTxt"]').hide();
Msg = $('#chngPwdForm div[id="errorTxt"]').text("Password changed. Click on Login to continue.");
var formURL = $("#chngPwdForm").attr('action');
$.ajax({
url: formURL,
type: 'POST',
data: $(this).serialize(),
dataType: 'JSON'
})
.done (function(data, textStatus, jqXHR) {
if (!data.status) {
var Msg = "Password Not Changed. Password Change Failed.";
var element;
var errorObj = new Error("");
$.each(data.Msg, function(elemName, errMsg) {
element = $("#chngPwdForm").filter("#" + elemName);
errorObj.message = errMsg;
$(element).closest(".form-group").addClass("has-error");
errorObj.insertAfter(element);
});
}
$('#chngPwdForm div[id="errorTxt"]').text(Msg).show();
})
.fail (function(response){
var Msg = "chngPwd call failed. errorThrown: " + response;
var obj = JSON.stringify(response);
alert("obj is: " + obj);
$('#chngPwdForm div[id="errorTxt"]').text(Msg);
})
}
else {
$('#chngPwdForm div[id="errorTxt"]').text('Please Correct errors').show();
}
return false;
The JSON response from changepwd.php:
{\"status\":false,\"Msg\":{\"crntPwd\":\"Current Password may only contain a-z, A-Z, 0-9, ! or #\"}
JS error
"TypeError: errorObj.insertAfter is not a function" is thrown for
"errorObj.insertAfter(element);"
The implemented solution:
JQuery:
// New Code has leading and trailing *
$("#chngPwdForm").submit(function(event){
*$(".chngPwderr").removeClass("error").text('');* //used to remove old messages
event.preventDefault();
if ($("#chngPwdForm").valid() ) {
$('#chngPwdForm div[id="errorTxt"]').hide();
Msg = $('#chngPwdForm div[id="errorTxt"]').text("Password changed. Click on Login to continue.");
var formURL = $(this).attr('action');
$.ajax({
url: formURL,
type: 'POST',
data: $(this).serialize(),
dataType: 'JSON'
})
.done (function(data, textStatus, jqXHR) {
if (!data.status) {
var Msg = "Password Not Changed. Password Change Failed.";
var focusSet = false;
$.each(data.Msg, function(elemName, errMsg) {
*$("#" + elemName).attr("type", "text");
if (!$("#" + elemName).parent().hasClass("error")) {
$("#" + elemName).parent().append('<div class="chngPwderr error">' + errMsg + '</div>');
}
if (!focusSet) {
$("#" + elemName).focus();
focusSet = true;
}*
});
}
$('#chngPwdForm div[id="errorTxt"]').text(Msg).show();
})
I'm working on a project (in Django) where I have created a page to add data information about a file and then add the file itself.
When 'More datasets' button is clicked, it adds another field to upload another file.
This can be done to attach as many files as the end-user wants in one go.
What I need is to upload all the attached files once 'Upload data-sets' is clicked and individual progress bar should be displayed.
So far, I have run through multiple tutorials but came kinda close using Vitor Freitas's tutorial.
JS code :
$(function(){
/*$("#add_new_dataset").click(function(){
$(".file").click();
});*/
$(".file").fileupload({
dataType: 'json',
sequentialUploads: true, /* Send the files one by one */
start: function(e){ /* When the upload process starts, show the modal */
$("#modal-progress").modal("show");
},
stop: function(e){ /* When the upload progress finalizes, hide the modal */
$("#modal-progress").modal("hide");
},
progressall: function(e, data){ /* Update the progress bar */
var progress = parseInt(data.loaded / data.total * 100, 10),
strProgress = progress + "%";
$(".progress-bar").css({"width": strProgress});
$(".progress-bar").text(strProgress);
},
done: function(e, data){
if(data.result.is_valid){
$("#gallery tbody").prepend(
"<tr><td><a href='" + data.result.url + "'>" + data.result.name + "</a></td></tr>"
);
}
}
})
});
Template code :
<form id="form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="container-fluid" id="datasets" style="margin: 0px; padding: 0px;">
<div class="row" onfocusin="populateFilename(this);">
<label class="col-md-2">User Name :</label>
<input class="col-md-2" type="text" id="username" name="user_name" value="{{ user.username }}" readonly />
<label class="col-md-2">Data-set :</label>
<input class="col-md-2" type="text" placeholder="Enter dataset" name="dataset" required />
<label class="col-md-2">Creation Date :</label>
<input class="col-md-2" type="date" placeholder="YYYY-MM-DD" name="creation_date" required />
<label class="col-md-2">Beam Line:</label>
<input class="col-md-2" type="text" placeholder="Enter beam line" name="beamline" required />
<label class="col-md-2">Data-set file:</label>
<input class="col-md-2 file" type="file" name="file" data-url="{% url 'add_data_sets' %}" data-form-data='{"csrfmiddlewaretoken": "{{ csrf_token }}"}' required />
<label class="filename"></label>
<div class="modal fade" id="modal-progress" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Uploading...</h4>
</div>
<div class="modal-body">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%;">0%</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div style="align: center; margin-top: 5px;">
<div class="container btn-group btn-group-justified btn-group-lg">
<a onmouseup="addRow();" class="btn btn-outline-secondary" style="width: 50%;">More Datasets</a>
<button type="submit" class="btn btn-outline-primary" name="add_new_dataset" id="add_new_dataset">Submit Data-set</button>
</div>
</div>
What should I do? I'm not very good at AJAX but I can't see any code that works to send the data to the server side. Is it so or am I missing something? Kindly ignore my ignorance on the topic and thanks to all in advance.
EDIT :
The JS code has been rewritten based on some of the answers.
document.getElementById('add_new_dataset').onclick = function() {
$(this).preventDefault();
console.log('Files uploading begin');
form_data = new FormData();
const files = document.getElementsByName('file');
let count = 0;
for(let i = 0; i < files.length; i++){
count++;
form_data.append("file", files[i]);
}
$.ajax({
url: "/add_data_sets",
dataType: 'json',
contentType: false,
processData: false,
data: form_data,
type: 'POST',
success: function(files, response, xhr, pd){
$('.file').show();
if(files.status != false){
$('.progress-bar').val('/location/' + files.filename);
var fileData = files.filename;
console.log('Files uploading...');
}
else{
alert(files.filename);
}
},
/*xhrFields: {
onprogress: function(e) {
if(e.lengthComputable) {
let percentCompleted = e.loaded / evt.total;
pBar.style.width = percentComplete;
pBar.innerHTML = percentComplete + "%";
console.log('Percent complete : ' + percentComplete);
}
}
}*/
xhr: function(){
let xhr = $.ajaxSettings.xhr();
xhr.upload.onprogress = function(e) {
let percentCompleted = e.loaded / evt.total * 100;
pBar.style.width = percentComplete;
pBar.innerHTML = percentComplete + "%";
console.log('Percent complete : ' + percentComplete);
};
return xhr;
}
});
//});
};
This is just the upload code block. The sending of the data from client-side to server-side works perfectly but it makes be suspicious as the 'console.log' calls aren't being triggered when the code runs through it. Is it so that the data is, somehow, being submitted normally and this code is doing nothing.
EDIT2 :
A new JS function:
function upload() {
console.log('Upload function begins');
let pBar = document.getElementsByClassName('progress-bar')[0],
progressWindow = document.getElementById('modal-progress'),
formData = new FormData(document.forms.form),
xhr = new XMLHttpRequest(),
percent = 0;
console.log('Form Data created');
// Start upload
xhr.upload.onloadstart = function() {
//$('#modal-progress').hide().fadeIn();
//progressWindow
};
// Track upload progress
xhr.upload.onprogress = function(event) {
percent = parseInt(event.loaded / event.total * 100);
pBar.innerHTML = percent + "%";
pBar.style.width = percent + "%";
//console.log(percent + '% completed');
//console.log('Uploaded event.loaded of event.total');
};
// Report if ends with an error
xhr.upload.onerror = function() {
console.log('An error has occurred')
};
// Track completion: Both successful or not
xhr.upload.onloadend = function() {
//$('#modal-progress').fadeOut().hide();
console.log('Upload complete with or without error ' + xhr.status);
};
// Track progress: Triggered on successful completion
xhr.upload.onload = function() {
console.log('Uploading complete');
progressWindow.hidden = True;
};
xhr.open("POST", "{% url 'add_data_sets' %}", true);
// The 'setRequestHeader' function can only be called when xhr is opened.
//xhr.setRequestHeader('csrfmiddlewaretoken', '{{ csrf_token }}');
//xhr.setRequestHeader('test-info', 'something');
xhr.setRequestHeader('Content-Type', 'application/gzip');
xhr.send(formData);
}
The function works fine now. It sends the data completely fine but on the development server console screen I get this error.
Forbidden (CSRF token missing or incorrect.): /accounts/add_data_set/
[22/Feb/2020 15:36:06] "POST /accounts/add_data_set/ HTTP/1.1" 403 2513
I even checked logged the POST data being send to the server and it does contain the csrf token
<QueryDict: {'csrfmiddlewaretoken': ['WREoIV0aY4B2XyrU7d9Qw8kMwiokXqwWsmbc2QSHX5VQ0EaYjjeuv7PeysMJjecp'], 'user_name': ['rakesh'], 'dataset': ['r'], 'creation_date': ['2020-02-22'], 'beamline': ['r']}>
I'm kind of confused. Is this an issue?
If you have any fileuploader plugin their documentations will have everything, or if you want normal file upload input you can post them by binding file input to form data and then post on action which will manipulate form data and save images, then you can return save image and display, this way you can achieve simple ajax upload.
var form_data = new FormData();
var totalFiles = document.getElementById('file').files.length;
var count = 0;
for (var i = 0; i < totalFiles; i++) {
var file = document.getElementById('file').files[i];
count++;
form_data.append("file", file);
}
$.ajax({
url: "/uploadingaction",
dataType: 'json',
contentType: false,
processData: false,
data: form_data,
type: 'POST',
success: function (files, response, xhr, pd) {
$('yourloaderid').hide();
if (files.status != false) {
$('#displayid').val('/location/' + files.filename);
var filedata = files.filename;
} else {
alert(files.filename);
}
}
})
I am having difficulty understanding why my form submits successful when i disable the ajax function. When i enable ajax on my input form. Input values i fill are not submitted. I used chrome developer tools to check the FormData being submitted. I realized nothing was being submit.
Hence i get an error asking me to fill those fields.
Controller
#PostMapping(value = "/basicAjax.json")
#ResponseBody
public ResponseEntity<Object> addCertJson(#Valid #ModelAttribute CertificateProgramme certificate, BindingResult result, Principal principal) {
User user = (User) ((UsernamePasswordAuthenticationToken) principal).getPrincipal();
certificate.setUser(user);
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
} else {
certificateService.save(certificate);
return new ResponseEntity<>(HttpStatus.ACCEPTED);
}
}
JS Script
var $form = $('.editCert');
var formData = $form.serialize();
var url = $form.attr('action');
var storage = {};
$.each($('.editCert').serializeArray(), function(i, field) {
storage[field.name] = field.value;
});
$form.on('submit',function(e){
e.preventDefault();
$.ajax(url,{
dataType: 'JSON',
data: storage,
type: "POST",
}).done(function(response){
if(response.status === 'SUCCESS'){
console.log('I am Okay' + response);
}else{
console.log('Holly Molly');
}
}).fail(function(jqXHR, textStatus, errorThrown){
var errorInfo="";
for(i =0 ; i <jqXHR.responseJSON.length ; i++){
errorInfo += "<br>" + (i + 1) +". " + jqXHR.responseJSON[i];
}
var $myerror = $form.find('.flash').addClass(' failure');
$myerror.html("Please correct following errors: " + errorInfo);
});
});
Form
<form method="post" th:object="${certificate}"
th:action="#{${action1}}" class="form-inline inline new-item editCert">
<input type="hidden" th:field="*{id}"/>
<div th:replace="common/layout :: flash"></div>
<div class="flash"></div>
<fieldset>
<legend th:text="${heading}"> Personal Information</legend>
<div class="row" th:classappend="${#fields.hasErrors('fullName')}? 'error' : ''">
<input type="text" class="form-control input-sm" th:field="*{fullName}"
placeholder="Full Name example Jane Doe"/>
<div class="error-message" th:if="${#fields.hasErrors('fullName')}" th:errors="*{fullName}"></div>
</div>
<div class="row" th:classappend="${#fields.hasErrors('gender')}? 'error' : ''">
<select th:field="*{gender}" class="form-control input-lg ">
<option value="">[Select Gender]</option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
<div class="error-message" th:if="${#fields.hasErrors('gender')}" th:errors="*{gender}"></div>
</div>
<div class="row" th:classappend="${#fields.hasErrors('date')}? 'error' : ''">
<input th:type="date" data-value="20/04/2015" class="form-control input-sm datepicker" th:field="*{date}"
placeholder="Date Of Birth"/>
<div class="error-message" th:if="${#fields.hasErrors('date')}" th:errors="*{date}"></div>
</div>
Solved my problem, Moved the form iteration into the submit $.ajax submit function
$form.on('submit',function(e){
e.preventDefault();
$.each($(this).serializeArray(), function(i, field) {
storage[field.name] = field.value;
});
console.log(storage);
$.ajax(url,{
//dataType: 'json',
data: storage,
type: "POST",
}).done(function(response){
console.log('I am Okay' + response);
}).fail(function(jqXHR, textStatus, errorThrown){
console.log(jqXHR);
if(jqXHR.status === 400){
var errorInfo="";
for(i =0 ; i <jqXHR.responseJSON.length ; i++){
errorInfo += "<br>" + (i + 1) +". " + jqXHR.responseJSON[i];
}
var $myerror = $form.find('.flash').addClass(' failure');
$myerror.html("Please correct following errors: " + errorInfo);
}
});
});
I have been trying to use the ng-file-upload module for sometime now and have met with considerable success.
My current requirement is to store 2 versions of the uploaded image, one the original and the other a thumbnail. My current code can store the thumbnail or the original but I can't find a way to do them both from the html. Is there any way to do this from the controller?
HTML:
<div class="row">
<div class="small-6 medium-4 large-4 columns">
<button class="button" type="file" ng-model="myActionsCtrl.image" ngf-select="myActionsCtrl.browseImage($file, $invalidFiles)" accept="image/*" ngf-max-size="2MB">Browse</button>
</div>
<div class="small-6 medium-4 large-4 columns">
<button class="button" ng-click="myActionsCtrl.uploadImage()">Upload</button>
</div>
</div>
My Controller:
module.exports = function ($scope, Upload, AuthService) {
let vm = this;
vm.user = AuthService.user
vm.uploadImage = function () {
if (vm.file) {
vm.file.upload = Upload.upload({
url: '/uploads/gallery',
method: 'POST',
file: vm.file
});
vm.file.upload.then(function (response) {
$timeout(function () {
vm.file.result = response.data;
});
}, function (response) {
if (response.status > 0) { }
// $scope.errorMsg = response.status + ': ' + response.data;
}, function (evt) {
vm.file.progress = Math.min(100, parseInt(100.0 *
evt.loaded / evt.total));
});
}
}
vm.browseImage = function (file, errFiles) {
$scope.file = file;
vm.file = file;
$scope.errFile = errFiles && errFiles[0];
}
}
I've modified the example code provided on jQuery File Upload's Wiki My scripting works for the add callback but not the done callback. The server is getting the post correctly and returning a JSON response.
I've noticed in the source code some of the callbacks are commented out. I'm not sure if I should uncomment them or not. Or use the callback fileuploaddone But removing the comment did not work.
Not sure if i'm doing this correctly. I'd like the server to return me a JSON object describing the image I just uploaded so the next step of my form can link the image with a backbone.js model.
<form id="uploadform">
<input id="fileupload" type="file" name="imagefile" data-url="imagefiles" multiple>
<button type="#" class="btn btn-primary uploadfile" style="display: none">Upload</button>
<div id="progress">
<div class="bar" style="width: 0%;"></div>
</div>
</form>
<script>
$(function () {
$('#fileupload').fileupload({
dataType: 'json',
done: function (e, data) {
data.context = $('.uploadfile').css('display','none')
utils.addValidationSuccess('Added file: ' + data.jqXHR.name);
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .bar').css(
'width',
progress + '%'
);
},
add: function (e, data) {
console.log('added');
data.context = $('.uploadfile')
.css('display','block')
.click(function () {
utils.showAlert('Uploading','...', 'alert-warning');
data.submit();
});
}
});
});
</script>
What got things working was using jquery.ajax 's apparently native callback on submit, adjusted code shown below.
<div class="row-fluid">
<form id="uploadform">
<input id="fileupload" type="file" name="imagefile" data-url="imagefiles" multiple>
<button type="#" class="btn btn-primary uploadfile" style="display: none">Upload</button>
<div id="progress">
<div class="bar" style="width: 0%;"></div>
</div>
</form>
<script>
$(function () {
$('#fileupload').fileupload({
dataType: 'json',
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .bar').css(
'width',
progress + '%'
);
},
add: function (e, data) {
console.log('added');
data.context = $('.uploadfile')
.css('display','block')
.click(function () {
utils.showAlert('Uploading','...', 'alert-warning');
var jqXHR = data.submit()
.success(function (result, textStatus, jqXHR) {
console.log('Done');
console.log('e:' + e);
console.log('results:' + result );
console.log('results.id:' + result.id );
console.log('textStatus:' + textStatus );
console.log('jqXHR:' + jqXHR );
data.context = $('.uploadfile').css('display','none')
utils.showAlert('Success','the file uploaded successfully','alert-success');
// utils.addValidationSuccess('Added file: ' + data.jqXHR.name);
})
.error(function (jqXHR, textStatus, errorThrown){
utils.showAlert('Error','...', 'alert-error');
});
});
}
});
});
</script>
</div>
I had the same problem with this code.
$(function () {
$('#fileupload').fileupload({
dataType: 'json',
done: function (e, data) {
alert("done");
}
});
});
Just with not setting the dataType, the done callback is now executed ...
Code below just work ...
$(function () {
$('#fileupload').fileupload({
done: function (e, data) {
alert("done");
}
});
});
The server return some json.