jQuery File Upload in Rails 4.x Nested Form - javascript

I have a Horse model and a Photo model. I am using jQuery File Upload to resize (client side) images and save on Amazon s3 directly since I am using Heroku.
I have seen other questions similar that use carrierwave, paperclip, or that are very old. I am not sure why you would use carrierwave/paperclip, but I think based on what heroku says, I do not want to have images hitting the server potentially causing time-outs.
Heroku recommends using jQuery File Upload and shows js appending new file input with a value of the image's link (returned from amazon s3). I have this working when saving a photo separately. I now want to make it work in a nested form for Horse but js is not finding input (since it does not exist yet because it's nested I presume).
I am using Cocoon for nested forms (I am open to anything that will work better). I am not too familiar with javascript/jQuery but a far as I can tell, Cocoon 'hides' the nested element until I click to add it via the add_association.
haml view code:
= link_to_add_association 'add photo', f, :photos
html source before clicking 'add photo'
<div class='links'>
<a class="btn btn-default btn-sm add_fields" data-association-insertion-method="after" data-association="photo" data-associations="photos" data-association-insertion-template="<div class='nested-fields'>
<fieldset class="inputs"><ol><input type="file" name="horse[photos_attributes][new_photos][url]" id="horse_photos_attributes_new_photos_url" />
<input type="hidden" name="horse[photos_attributes][new_photos][_destroy]" id="horse_photos_attributes_new_photos__destroy" value="false" /><a class="remove_fields dynamic" href="#">remove photo</a>
</ol></fieldset>
</div>
" href="#">add photo</a>
How do I work with this input and how do I handle multiple file uploads as they are added correctly?
My current upload js:
$(function() {
if ($('#new_horse').length > 0) {
$.get( "/presign", function( s3params ) {
$('.direct-upload').find("input:file").each(function(i, elem) {
var fileInput = $(elem);
var form = $(fileInput.parents('form:first'));
var submitButton = form.find('input[type="submit"]');
var progressBar = $("<div class='bar'></div>");
var barContainer = $("<div class='progress'></div>").append(progressBar);
fileInput.fileupload({
fileInput: fileInput,
url: "http://" + s3params.url.host,
type: 'POST',
autoUpload: true,
formData: s3params.fields,
paramName: 'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
dataType: 'XML', // S3 returns XML if success_action_status is set to 201
disableImageResize: false,
imageQuality: 0.5,
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator && navigator.userAgent),
imageMaxWidth: 500,
imageMaxHeight: 1000,
imageOrientation: true, //auto rotates images
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, //I added this to jquery.fileupload-validate: alert('Must Be JPG GIF or PNG Image')
replaceFileInput: false,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
submitButton.prop('disabled', true);
fileInput.after(barContainer);
progressBar.
css('background', 'green').
css('display', 'block').
css('width', '0%').
text("Loading...");
},
done: function(e, data) {
submitButton.prop('disabled', false);
progressBar.text("Pre-uploading done... Please Save or Cancel");
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = 'https://' + s3params.url.host +'/' + key;
// remove first input to prevent phantom upload delay
fileInput.remove();
// create new hidden input with image url
var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
var imgPreview = '<img src="' + url + '">';
form.append(input);
form.append(imgPreview);
},
fail: function(e, data) {
submitButton.prop('disabled', false);
progressBar.
css("background", "red").
text("Failed");
}
});
});
}, 'json');
}
});

I guess I should have looked at cocoon documentation first:
http://www.rubydoc.info/gems/cocoon#Callbacks__upon_insert_and_remove_of_items_
http://www.rubydoc.info/gems/cocoon/1.2.6
I modified my upload.js file to the following and it worked for multiple files in nested forms perfectly:
// added for file uploading
// https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails
// Get our s3params from our endpoint
$(document).on('ready page:load', function () {
$('.direct-upload')
.on('cocoon:after-insert', function(e, photo) {
console.log('inside cocoon image function');
$.get( "/presign", function( s3params ) {
$('.direct-upload').find("input:file").each(function(i, elem) {
console.log('inside nested-fields photo input form');
var fileInput = $(elem);
var form = $(fileInput.parents('form:first'));
var submitButton = form.find('input[type="submit"]');
var progressBar = $("<div class='bar'></div>");
var barContainer = $("<div class='progress'></div>").append(progressBar);
fileInput.fileupload({
fileInput: fileInput,
url: "http://" + s3params.url.host,
type: 'POST',
autoUpload: true,
formData: s3params.fields,
paramName: 'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
dataType: 'XML', // S3 returns XML if success_action_status is set to 201
disableImageResize: false,
imageQuality: 0.5,
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator && navigator.userAgent),
imageMaxWidth: 500,
imageMaxHeight: 1000,
imageOrientation: true, //auto rotates images
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, //I added this to jquery.fileupload-validate: alert('Must Be JPG GIF or PNG Image')
replaceFileInput: false,
previewMaxWidth: 100,
previewMaxHeight: 100,
previewCrop: true,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
submitButton.prop('disabled', true);
fileInput.after(barContainer);
progressBar.
css('background', 'green').
css('display', 'block').
css('width', '0%').
text("Loading...");
},
done: function(e, data) {
submitButton.prop('disabled', false);
progressBar.text("Photo Uploaded");
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = 'https://' + s3params.url.host +'/' + key;
// remove first input to prevent phantom upload delay
fileInput.remove();
// create new hidden input with image url
var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
var imgPreview = '<img src="' + url + '">';
form.append(input);
form.append(imgPreview);
},
fail: function(e, data) {
submitButton.prop('disabled', false);
progressBar.
css("background", "red").
text("Failed");
}
}, 'json'); //fileupload
}); //each file
}); //presign call
}); // function cocoon
}); // page ready
I guess Google and a well documented gem can replace knowledge of JS in the short term :-) I am sure it's not at tight as it could be so please offer any improvements.

Related

Change Dropzone maxFiles Dynamically

I'm trying to dynamically update the MaxFiles property each time a new image is uploaded/deleted.
By using the following code its not allowing any image to upload instead of limitize it to maxFiles. And it is not taking the value of the variable maxFile, but when i remove maxFile variable And put a number then it works fine.
got source code idea from this Answer.
!function ($) {
"use strict";
var Onyx = Onyx || {};
Onyx = {
init: function() {
var self = this,
obj;
for (obj in self) {
if ( self.hasOwnProperty(obj)) {
var _method = self[obj];
if ( _method.selector !== undefined && _method.init !== undefined ) {
if ( $(_method.selector).length > 0 ) {
_method.init();
}
}
}
}
},
userFilesDropzone: {
selector: 'form.dropzone',
init: function() {
var base = this,
container = $(base.selector);
base.initFileUploader(base, 'form.dropzone');
},
initFileUploader: function(base, target) {
var maxFile = $('.dropzone').attr('data-count');
var onyxDropzone = new Dropzone(target, {
url: ($(target).attr("action")) ? $(target).attr("action") : "data.php", // Check that our form has an action attr and if not, set one here
maxFiles: maxFile,
maxFilesize: 5,
acceptedFiles: ".JPG,.PNG,.JPEG",
// previewTemplate: previewTemplate,
// previewsContainer: "#previews",
clickable: true,
uploadMultiple: false,
});
onyxDropzone.on("success", function(file, response) {
let parsedResponse = JSON.parse(response);
file.upload_ticket = parsedResponse.file_link;
var imagecount = $('.dropzone').attr('data-count');
imagecount = imagecount - 1;
$('.dropzone').attr('data-count', imagecount);
});
},
}
}
}// JavaScript Document
function openImagePopup(id = null) {
$(".upload-images").show();
$.ajax({
url: 'fetch.php',
type: 'post',
data: {id: id},
dataType: 'json',
success:function(response) {
var imagecount = response.counts;
$('.dropzone').attr('data-count', imagecount);
}
});
}
HTML
<form action="data.php" class="dropzone files-container" data-count="">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
<input type="hidden" id="imageId" name="imageId">
</form>
UPDATED ANSWER
Once instanciated, the Dropzone plugin will remains with the same options unless you change the instance inner options directly.
To change options of a Dropzone, you can do this with the following line:
$('.dropzone')[0].dropzone.options.maxFiles = newValue;
$('.dropzone')[0] returns the first dropzone DOM element
.dropzone.options return the underlying plugin instance options of the Dropzone. You can now change any options directly on this object.
In you case, you will have to change the function that initiate the popup like follow
function openImagePopup(id = null) {
$(".upload-images").show();
$.ajax({
url: 'fetch.php',
type: 'post',
data: {id: id},
dataType: 'json',
success:function(response) {
var imagecount = response.counts;
$('.dropzone')[0].dropzone.options.maxFiles = imagecount;
}
});
}
And change the dropzone onSuccess event like this:
onyxDropzone.on("success", function(file, response) {
let parsedResponse = JSON.parse(response);
file.upload_ticket = parsedResponse.file_link;
var imagecount = $('.dropzone')[0].dropzone.options.maxFiles - 1;
$('.dropzone')[0].dropzone.options.maxFiles = imagecount;
});
As you can see, You can also remove the data-count="" attribute on you element and reuse the value from the plugin instance options.maxFiles
After spending a couple of hours of trials and errors I realized using the maxFiles setting from Dropzone is not exactly what is expected in many cases. That setting will only limit uploading files through the explorer / drag&drop, but after reload more files can be uploaded. It also does not reflect any failures to the upload on the serrver side (e.g. file size too big).
Changing the value of the maxFiles setting of an already initialized Dropzone from outside ot it is impossible. For example reseting the number of allowed files after removing some images with ajax will not work.
To really control the number of files that can be uploaded to the server the counting must take place on the server. Then in the Dropzone, in the success function, we should handle the ajax response:
success: function (file, response) {
var response_data = jQuery.parseJSON(response);
if(!response_data.success) {
$(file.previewElement).addClass('dz-error');
$(file.previewElement).addClass('dz- complete');
$(file.previewElement).find('.dz-error-message').text(response_data.error);
}
}
The response is the feedback information provided by the script assigned to the action attribute of the Dropzone <form>, e.g. <form action="/uploader">.

how to upload multiple images with resizing in jquery to django backend server

I am using Jquery file upload and compressing code for images with this i can select multiple images but it only compressing and uploading the last image selected
so how can i upload multiple images with compression? What modifications are needed plzz suggest.
Html code:
<input id="fileupload" type="file" name="file" multiple>
Jquery code:
function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$(function () {
'use strict';
var csrftoken = $.cookie('csrftoken');
var url = '/dashboard/{{name}}/{{name_product_type.type_product|urlencode}}/{{abc}}/';
$('#postbtn').on('click', function () {
var $this = $(this),
data = $this.data();
$this
.off('click')
.text('Abort')
.on('click', function () {
$this.remove();
data.abort();
});
data.submit().always(function () {
$this.remove();
});
});
$('#fileupload').fileupload({
url: url,
crossDomain: false,
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
},
dataType: 'json',
uploadMultiple: true, // allow multiple upload
autoProcessQueue: false, // prevent dropzone from uploading automatically
maxFiles: 5,
autoUpload: false,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
maxFileSize: 5000000, // 5 MB
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator.userAgent),
previewMaxWidth: 100,
previewMaxHeight: 100,
previewCrop: true
}).on('fileuploadadd', function (e, data) {
data.context = $('<div/>').appendTo('#files');
$.each(data.files, function (index, file) {
var node = $('<p/>')
.append($('<span/>').text(file.name));
if (!index) {
node
.append('<br>')
// .append($('#postbtn').clone(true).data(data));
}
node.appendTo(data.context);
});
}).on('fileuploadprocessalways', function (e, data) {
var index = data.index,
file = data.files[index],
node = $(data.context.children()[index]);
if (file.preview) {
node
.prepend('<br>')
.prepend(file.preview);
}
if (file.error) {
node
.append('<br>')
.append(file.error);
}
if (index + 1 === data.files.length) {
data.context.find($('#postbtn').data(data));
// .text('Upload')
// .prop('disabled', !!data.files.error);
}
}).on('fileuploadprogressall', function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .progress-bar').css(
'width',
progress + '%'
);
}).on('fileuploaddone', function (e, data) {
$.each(data.result.files, function (index, file) {
var link = $('<a>')
.attr('target', '_blank')
.prop('href', file.url);
$(data.context.children()[index])
.wrap(link);
});
}).on('fileuploadfail', function (e, data) {
$.each(data.result.files, function (index, file) {
var error = $('<span/>').text(file.error);
$(data.context.children()[index])
.append('<br>')
.append(error);
});
}).prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');
});
Django backend :
in views.py :
files=request.FILES.getlist('file')
and then save it to database.
Everrthing works fine. Even image is compressed at the output but we intent to have multiple images compressed and save back to database at one hit of button.
Any sugestions are welcomed...
Thank you...

jQuery-File-Upload- show selected file physical path

i am using this plug in for file upload
my problem is , when i click on
<input type="file" class="upload" id="imgAuthorImageUpload" accept="image/gif, image/jpeg,image/jpg,image/bmp,image/pjpeg,image/png" />
imgAuthorImageUpload - the popup will open from explore and it will let you chose the file for upload
as soon as it start to upload, i want to show that file location on page,
for example,
if i chose animal.png from d drive picture folder: then i want to display something like this
D:/Pictures/animal.png
the code i wrote like this :
$("#imgAuthorImageUpload").fileupload({
url: '/picture/DashBoard/pictureUpload.ashx',
dataType: 'json',
cache: false,
async: true,
}).on('fileuploadadd', function (e, data) {
e.preventDefault();
}).on('fileuploaddone', function (e, data) {
alert("upload done");
e.preventDefault();
}).on('fileuploadprogress', function (e, data) {
var percentVal = '0%';
var percentVal = parseInt(data.loaded / data.total * 100, 10);
$('.bar').css(
'width',
percentVal + '%'
);
$('.percent').html(percentVal + '%');
}).on('fileuploadcomplete', function (e, data) {
alert("Upload complete");
});
For security reasons, browsers don't allow this.
A good explanation is provided here: Full path from file input using jQuery

How can i check number of files selected with Fine Uploader?

I am using manual uploading via Fine Uploader. Now i want to check that file has selected or not.
$(document).ready(function() {
var fineuploader = new qq.FineUploader({
element: $('#fine-uploader')[0],
request: {
endpoint: '<?php echo site_url('pl_items/upload_images');?>'
},
multiple: true,
autoUpload: false,
onLeave: false,
validation: {
allowedExtensions: ['jpeg', 'jpg', 'gif', 'png'],
sizeLimit: 5120000 // 50 kB = 50 * 1024 bytes
},
text: {
uploadButton: '<i class="icon-plus icon-white"></i> Select Files'
},
template: '<div class="qq-uploader">' +
'<pre class="qq-upload-drop-area"><span>{dragZoneText}</span></pre>' +
'<div class="qq-upload-button btn btn-danger">{uploadButtonText}</div>' +
'<div class="qq-drop-processing span1"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></div>' +
'<div><ul class="qq-upload-list"></ul></div>' +
'</div>',
callbacks: {
onComplete: function(id, name, response) {
$('#frmDetails').append('<input type="hidden" name="pl_item_images[]" value="'+response.file_name+'">');
//$("#frmDetails").submit();
}
},
});
$('#submit_button').click(function() {
fineuploader.uploadStoredFiles();
});
});
Since you haven't responded to my question, I'll just assume that you want to determine if a file has been selected before you call uploadStoredFiles in your click handler.
It's really very simple. Just make use of the getUploads API method. For example, you could change your click handler to look like this:
$('#submit_button').click(function() {
var submittedFileCount = fineuploader.getUploads({status: qq.status.SUBMITTED}).length;
if (submittedFileCouunt > 0) {
fineuploader.uploadStoredFiles();
}
});
A few more things:
There is no onLeave option. You should remove this from your code.
The multiple option defaults to true. You can remove this from your code as well.
You are already using jQuery. Why aren't you using the Fine Uploader jQuery plug-in? See the documentation for instructions.

How to append files to formData

I am obtaining files and their values in a non-normal way. There isn't an actual input in the form. Therefore, I am trying to append the files to the formData for ajax submission.
Anytime I try to submit the form with the method below, my files aren't uploading. Therefore, the way I am appending the files must be incorrect.
I was told the following from someone, but I can't figure out how to do it:
You're looping through the array but appending the entire array every
time through the loop. Use the brackets on the form input name and
append each file in the array.
Does anyone see what I need to change to get this to work?
Code for the dropzone...before the relevant code below:
var dragFileName = '';
var myDropzone = new Dropzone("#myDropzone", {
//$('#myDropzone').dropzone ({
//Dropzone.options.myDropzone= {
url: 'php/quoteSendTest.php',
autoProcessQueue: false,
paramName: "file",
uploadMultiple: true,
parallelUploads: 5,
maxFiles: 5,
maxFilesize: 25,
acceptedFiles: 'image/*',
addRemoveLinks: true,
dictFileTooBig: 'File is larger than 25MB',
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.
console.log("Something should be showing for eventListener");
//e.preventDefault();
e.stopPropagation();
dzClosure.processQueue();
});*/
this.on("addedfile", function(file) {
/* Maybe display some more file information on your page */
dragFileName = file.name;
var dragFileSize = file.size;
var count = myDropzone.files.length;
console.log('File added - ' + file.name + ' - Size - ' + file.size);
console.log(count + " is the length");
//console.log("FILEname is " + dragFileName);
setTimeout(function () {
toggleUploadButton();
}, 10);
});
//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());
});
*/
}
});
Relevant code:
var acceptedFiles = null;
var allAcceptFiles = null;
function toggleUploadButton() {
acceptedFiles = myDropzone.getAcceptedFiles();
allAcceptFiles = acceptedFiles.values();
for (let fileElements of allAcceptFiles) {
console.log(fileElements);
}
}
function submit(){
var form = document.getElementById("salesforce_submit");
var formData = new FormData(form);
fileElements.each(function() {
formData.append('uploadedFile[]', fileElements);
});
alert(formData);
$.ajax({
url: '/php/quoteSendTest.php',
type: 'POST',
data: formData,
HTML:
<form action="<?php echo $config['sf_url']; ?>" method="POST" id="salesforce_submit">
<input id="first_name" name="first_name" type="text">
<div class="dropzone dz-clickable" id="myDropzone">
<div class="dz-default dz-message dG">Drop files here or click to upload</div>
</div>
<button type="submit" id="submit-all">SEND PROJECT QUOTE</button>
</form>
Console.log info from fileElements:
File {upload: {…}, status: "queued", previewElement:
div.dz-preview.dz-file-preview, previewTemplate:
div.dz-preview.dz-file-preview, _removeLink: a.dz-remove, …} accepted:
true dataURL: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABtUA"
height: 892 lastModified: 1512405192880 lastModifiedDate: Mon Dec 04
2017 11:33:12 GMT-0500 (Eastern Standard Time) {} name:
"analytics.PNG" previewElement: div.dz-preview.dz-image-preview
previewTemplate: div.dz-preview.dz-image-preview size: 544438 status:
"queued" type: "image/png" upload: {uuid:
"6dc946e7-e9db-4b3a-88af-b790de1c2975", progress: 0, total: 544438,
bytesSent: 0, filename: "analytics.PNG", …} webkitRelativePath: ""
width: 1749
_removeLink: a.dz-remove
proto: File
In your initial attempt:
function toggleUploadButton() {
acceptedFiles = myDropzone.getAcceptedFiles();
allAcceptFiles = acceptedFiles.values();
for (let fileElements of allAcceptFiles) {
console.log(fileElements);
}
}
var formData = new FormData(form);
fileElements.each(function() {
formData.append('uploadedFile[]', fileElements);
});
You are looping through allAcceptFiles and setting each one to fileElements. This leaves fileElements as a single file, and when try to do the each loop later it doesn't act as you'd expect.
I noticed that myDropzone must be defined somewhere, since it was working in the first function. Looking at the dropzone documentation, I saw it had a getAcceptedFiles method that you could easily use to loop through and add each file to the form data. The modified loop is below:
var formData = new FormData(form);
myDropzone.getAcceptedFiles().forEach(file => {
formData.append("uploadedFile[]", file);
});
There are a couple other things that don't seem necessary in the code, but this isn't code review so I'll leave them alone.

Categories

Resources