Davie Overman
Davie Overman

Reputation: 114

Why do i received Uncaught TypeError: fileInput.fileupload is not a function using Ruby on Rails and JQuery?

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

Answers (1)

lacostenycoder
lacostenycoder

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

Related Questions