I am following the https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails#jquery-file-upload-callbacks Tutorial to use s3_direct_upload on local development environment. There's some javascripts which set the fileupload function for each file fields. It all works great if I simply include the codes right below the form, but it just doesn't work if I put the code in a js.erb file under app/assets/javascripts/
. The error is:
ActionView::Template::Error (undefined method `url' for nil:NilClass
(in /s3_direct_upload_gem_test/app/assets/javascripts/s3_direct_upload.js.erb)):
18:
19: <%= @s3_direct_post.url %>
20:
21: <%= javascript_include_tag :s3_direct_upload %>
app/assets/javascripts/s3_direct_upload.js.erb:14:in `block in singleton class'
As you may see, line 19
from the above code is used to print out the @s3_direct_post.url. It was correctly printed when I include all the javascript in the file. If I trace the line 14 in the s3_direct_upload.js.erb
, it is the url
line:
fileInput.fileupload({
fileInput: fileInput,
url: '<%= @s3_direct_post.url %>',
type: 'POST',
autoUpload: true,
formData: <%= @s3_direct_post.fields.to_json.html_safe %>,
It seems like that for some reason, the javascript file under the assets
folder (in the asset pipeline) is compiled separately and so @s3_direct_post
, which is set in the Controller
is not set here.
Of course I can always put those in <script>
at the viewer file but it is not quite elegant. Is there any way to separate page-specific js coding and solve the problem above?
All JS files within /assets/javascript are part of the asset pipeline. In production the asset pipeline is compiled beforehand when your Rails app is deployed (e.g not on each request). This is why @s3_direct_post.url
is nil.
I agree that injecting the whole JS code in the view is less than ideal and not very elegant. In the past I have come up with approaches that take inspiration from JS frameworks like Google Analytics, where only 2-3 lines of JS are placed in the HTML:
/* /assets/javascripts/s3_direct_upload.js */
window.MyApp = {};
window.MyApp.config = {};
window.MyApp.config.getS3Url = function() {
if(typeof(window.MyApp.config._s3Url) == ‘undefined’) {
throw “No S3 URL configured”;
}
return window.MyApp.config._s3Url;
};
window.MyApp.config.setS3Url = function(url) {
window.MyApp.config._s3Url = url;
}
// ...
$('#upload-button').on('click', function(){
fileInput.fileupload({
fileInput: fileInput,
url: window.MyApp.config.getS3Url(),
type: 'POST',
autoUpload: true
})
});
Then the view only needs to reference the Config API you've created:
<script type=“text/javascript”>
window.MyApp.config.setS3Url('<%= @s3_direct_post.url %>');
</script>
However if you're really determined not to have any JS in the views. You could load the configs via a dynamic JSON request:
class JSConfigsController < ApplicationController
def index
configs = {
's3URl' => @s3_direct_post.url
# etc
}
respond_to do |f|
f.json do
render json: {config: configs} # => {"config": {"s3Url": "http://the_url"}}
end
end
end
end
Then you can load all the configs via ajax by requesting /js_configs.json
. However this approach requires a bit more care due to the asynchronous nature of Ajax. E.g you have to be careful to not call JS functions that rely on configs until the Ajax request has finished being retrieved.
finally I work around this problem by using another s3 related gem: s3_file_field and it just works well even without heavy tweaking. unfortunately I can't find all related information through my study on solving this problem.
I hope this help!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With