Reputation: 114
I was following the "Direct to S3 Image Uploads in Rails". My goal is to upload documents directly to Amazon S3 via the form and save the corresponding s3 link into the UserDocument model.
I'm using ruby 2.2.10p489 and Rails 5.1.6. I'm trying to upload files directly to Amazon S3 using JQuery. Upon loading the new.html.erb view, I get the following error message in the Chrome javascript console:
Uncaught TypeError: fileInput.fileupload is not a function
at HTMLInputElement.<anonymous> (client_side_s3_upload.self-2be7ed022d6f0781280d316c208a0c078031b2d12aee201b25082ec22be186e6.js:10)
at Function.each (jquery-3.3.1.self-5af507e253c37e9c9dcf65064fc3f93795e6e28012780579975a4d709f4074ad.js:355)
at jQuery.fn.init.each (jquery-3.3.1.self-5af507e253c37e9c9dcf65064fc3f93795e6e28012780579975a4d709f4074ad.js:190)
at HTMLDocument.<anonymous> (client_side_s3_upload.self-2be7ed022d6f0781280d316c208a0c078031b2d12aee201b25082ec22be186e6.js:3)
at fire (jquery.self-bd7ddd393353a8d2480a622e80342adf488fb6006d667e8b42e4c0073393abee.js:3233)
at Object.fireWith [as resolveWith] (jquery.self-bd7ddd393353a8d2480a622e80342adf488fb6006d667e8b42e4c0073393abee.js:3363)
at Function.ready (jquery.self-bd7ddd393353a8d2480a622e80342adf488fb6006d667e8b42e4c0073393abee.js:3583)
at HTMLDocument.completed (jquery.self-bd7ddd393353a8d2480a622e80342adf488fb6006d667e8b42e4c0073393abee.js:3618)
Here is the new.html.erb view pertaining to the upload form:
<%= javascript_include_tag 'jquery-3.3.1.js' %>
<%= javascript_include_tag 'jquery.ui.widget.js' %>
<%= javascript_include_tag 'z.jquery.fileupload.js' %>
<% if !@user_document.errors.empty? %>
<div class = "alert alert-error">
<ul>
<% @user_document.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class = "well">
<%= form_for(@user_document, html: { class: 'directUpload', data: { 'form-data' => (@s3_direct_post.fields), 'url' => @s3_direct_post.url, 'host' => URI.parse(@s3_direct_post.url).host } }) do |f| %>
<div class="field">
<%= f.label :attachment %>
<%= f.file_field :attachment %>
<%= f.submit "Save", class: "btn btn-primary" %>
</div>
<% end %>
</div>
Here is my application.html.erb:
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag Ckeditor.cdn_url %>
<script src="https://js.stripe.com/v3/"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<style type="text/css">
.bs-example{
margin: 20px;
}
</style>
<!== [if lt IE 9]>
<script src = "cdnjs.cloudfare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
</head>
<body>
<div class="container">
<% flash.each do |message_type, message| %>
<div class="alert alert-info alert-<%= message_type %>"><%= message %></div>
<% end %>
<%= yield %>
</div>
</body>
</html>
Here is the relevant controller:
class UserDocumentsController < ApplicationController
before_action :logged_in_user
before_action :set_s3_direct_post, only: [:new, :edit, :create, :update]
def new
@user_document = UserDocument.new
end
def create
@user_document = UserDocument.new(user_document_params)
if @user_document.save
redirect_to user_documents_path, notice: "The document #{@user_document.name} has been uploaded."
else
render "new"
end
end
def destroy
@user_document = UserDocument.find(params[:id])
@user_document.destroy
redirect_to user_documents_path, notice: "The document #{@user_document.name} has been deleted."
end
private
def user_document_params
params.require(:user_document).permit(:name, :attachment)
end
def set_s3_direct_post
@s3_direct_post = S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'private')
end
end
Here is the javascript:
$(function() {
$('.directUpload').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.after(barContainer);
fileInput.fileupload({
fileInput: fileInput,
url: form.data('url'),
type: 'POST',
autoUpload: true,
formData: form.data('form-data'),
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
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);
progressBar.
css('background', 'green').
css('display', 'block').
css('width', '0%').
text("Loading...");
},
done: function(e, data) {
submitButton.prop('disabled', false);
progressBar.text("Uploading done");
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = '//' + form.data('host') + '/' + key;
// create hidden field
var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
form.append(input);
},
fail: function(e, data) {
submitButton.prop('disabled', false);
progressBar.
css("background", "red").
text("Failed");
}
});
});
});
Upvotes: 1
Views: 5168
Reputation: 11226
UPDATE:
You really need to learn jquery if you're going to make any of this work. You have many points of possible failure so try to listen to what the error messages tell you. There are couple of ways you can debug your broken JS
throw a debugger in and go in the console to see what your objects are and what functions can be called on them. I'd start here:
$('.directUpload').find("input:file").each(function(i, elem) {
var fileInput = $(elem);
debugger;
See if fileInput
has the function defined fileUpload
if so keep going and move debugger down line by line.
Without a solid understanding of jquery you're gonna struggle with this. Without seeing the actual view in the browser, I'm limited to what I can help you debug.
First answer:
This may not be the answer you want, but can't you just use https://github.com/carrierwaveuploader/carrierwave instead?
But also get rid of this in application controller as you have a different version of jquery loading with javascript_tag
get rid of other stuff in application.html and put this. Bootstrap requires jquery so make sure jquery comes first
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag Ckeditor.cdn_url %>
<%= javascript_include_tag "https://js.stripe.com/v3/" %>
<!--- it would be better if you move these to qpplication.js --->
<%= javascript_include_tag 'jquery-3.3.1.js' %>
<%= javascript_include_tag 'jquery.ui.widget.js' %>
<%= javascript_include_tag 'z.jquery.fileupload.js' %>
<%= javascript_include_tag 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' %>
<%= stylesheet_link_tag 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' %>
get rid of this stuff
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
You should not be trying to use 2 different versions of jquery in the same app. There could be version conflict issues between jquery version and the jQuery-File-Upload
you're using.
The rails standard has been to include required js files in application.js
and keep them in your vendor/assets
folder, or use a gem to include them.
Upvotes: 1