0

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="&lt;div class=&#39;nested-fields&#39;&gt;
  &lt;fieldset class=&quot;inputs&quot;&gt;&lt;ol&gt;&lt;input type=&quot;file&quot; name=&quot;horse[photos_attributes][new_photos][url]&quot; id=&quot;horse_photos_attributes_new_photos_url&quot; /&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;horse[photos_attributes][new_photos][_destroy]&quot; id=&quot;horse_photos_attributes_new_photos__destroy&quot; value=&quot;false&quot; /&gt;&lt;a class=&quot;remove_fields dynamic&quot; href=&quot;#&quot;&gt;remove photo&lt;/a&gt;
  &lt;/ol&gt;&lt;/fieldset&gt;
&lt;/div&gt;
" 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');
  }
});

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.