Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Got working with nested forms #79

Open
wants to merge 3 commits into from

10 participants

anark Wayne Kai Schlichting Ellis Berner Afzal Masood Lake Denman Adam Stankiewicz Nathan Youngman Forrest Zeisler gbenz
anark

A solution for #74

This allows you to use s3 direct upload without requiring a separate form. To use with just an input tag within an existing form you will need to do a couple things.

Set the url for s3 uploader since it can no longer get it from the form

  $("#myS3Uploader").S3Uploader(
    {
    url: '<%=S3DirectUpload.config.url%>'
  });

This requires a url to be set in your uploader config

You can then include a file input tag using the following helpers

<%= s3_uploader_hidden_fields%>
<%= s3_uploader_field callback_url: model_url, callback_param: "model[param_name]", id: "myS3Uploader"%>

However this will not work with the IE9 fix and I do not know anything about making it IE9 compatable

Wayne
Owner

Thank you for this, but I would rather not break ie 8 compatibility just yet. I'll keep this pull open in case we can figure out a solution.

Kai Schlichting

When I understand it correctly, we need forms to keep this gem compatible with IE <= 9? What are our options? I was thinking about inserting a hidden form somewhere on the page on the fly (e.g. as a child of the body tag). This way we don't have to nest forms anymore, but still keep the IE compatibility. Would this be possible?

Wayne waynehoover commented on the diff
lib/s3_direct_upload/form_helper.rb
@@ -72,7 +98,6 @@ def policy_data
{
expiration: @options[:expiration],
conditions: [
- ["starts-with", "$utf8", ""],
Wayne Owner

Why was this removed? If you use a Rails' form_tag it will create a hidden input element with this attribute by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Wayne waynehoover commented on the diff
app/assets/javascripts/s3_direct_upload.js.coffee
((6 lines not shown))
content = {}
if result # Use the S3 response to set the URL to avoid character encodings bugs
content.url = $(result).find("Location").text()
content.filepath = $('<a />').attr('href', content.url)[0].pathname
- else # IE <= 9 return a null result object so we use the file object instead
- domain = $uploadForm.attr('action')
- content.filepath = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
- content.url = domain + content.filepath + '/' + encodeURIComponent(file.name)
+ #else # IE <= 9 return a null result object so we use the file object instead
+ #domain = $uploaderElement.attr('action')
Wayne Owner

These three lines seem to work with IE, did you have problems here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Wayne
Owner

After looking over the code, I actually don't think we will have problems with IE here. What issues did you run up against with IE?

anark

We dont have problems with IE if we still use the form as the s3_uploader. However if you us a file_field then it will not work. If we use a file field $uploadForm will have not attribute action.

Ellis Berner

I found another neat workaround. Use a label for="id" where the input is in another form, the s3_upload_form and is positioned absolutely -9999px on the left. It works great in every browser except Firefox. Here's a crappy workaround for that though.

# Hack for Mozilla to click label for file inputs.
if $.browser.mozilla
  $(document).on "click", "label", (e) ->
    $(@control).click() if e.currentTarget is this and e.target.nodeName isnt "INPUT"
Afzal Masood

Hi,

I need lil help. I am facing an error:
undefined local variable or method `s3_uploader_hidden_fields'

Kai Schlichting

Any progress on this topic? It would be so great if we come up with a solution for this topic... If we can't solve the IE issue at the moment, what about a temporary if($uploaderElement.is('form')){...} switch so that we can at least use @anark changes in non-IE browsers?

Lake Denman

Just showing interest here. I'd like to see support for nested forms.

Thanks @waynehoover and @anark

Adam Stankiewicz

I'd like to see this feature too. Btw. @maletor solution is not working for me.

Adam Stankiewicz

OK, I made it work for IE7-10, probably 6 too:
https://github.com/sheerun/s3_direct_upload/tree/field-form

Nathan Youngman

I'd also like a built-in/documented way of handling file upload(s) in the context of a larger form. (likely setting a hidden field in the parent form, disabling submission while uploading...).

Forrest Zeisler

+1

Wayne
Owner

@sheerun would you be willing to send a pull based on your work?

Adam Stankiewicz

Well, I don't think it should be merged, it totally changes the way this plugin works (form -> field).

It's rather proof of concept for new gem like s3_upload_field or something.

Nathan Youngman

@sheerun Wouldn't it make sense to have s3_uploader_form and s3_uploader_fields in the same gem?

Adam Stankiewicz

That's more likely, but I need find time to rewrite the code :) I'll try to do it in a week or so. Also I don't like that gem doesn't provide success, start, error, and progress callbacks (in constructor), they would be helpful. Maybe implement them too? I'd like to make some use of promises too.

Adam Stankiewicz

I've re-written gem and released it under https://github.com/sheerun/s3_file_field

gbenz

@sheerun can you clarify this section? specifically...how would a form look that uses the s3_upload_form helper and the code snippet below? thank you!

Create a new view that uses the form helper s3_uploader_form:

= form_for :user do |form|
= form.s3_file_field :scrapbook, :class => 'js-s3_file_field'
.progress
.meter{ :style => "width: 0%" }

Adam Stankiewicz

@gbenz Only data attributes on <input type="file">

gbenz

sorry, I'm missing something...my form looks like this:

<%= form_for @post, :html => {:multipart => true} do |f| %>
<%= f.s3_file_field :avatar, :class=>"js-s3_file_field" %>
<%= f.submit button_label %>
<% end %>

...and I get the uninitialized constant: "NameError (uninitialized constant ActionView::Helpers::Tags)" for the s3_file_field

Thank you!

Adam Stankiewicz

Uh. I tested it mainly on Rails 4. Please wait a while for fix.

gbenz

ok, thanks!

Adam Stankiewicz

@gbenz Thank you so much for report. As it turned out I tested it against Rails 4 two times ;/ I've just released 1.0.2. Could you report if it works with Rails 3? I made little testing, so it should be all right.

https://github.com/sheerun/s3_file_field

gbenz

works now. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 10, 2013
  1. anark

    Got working with nested forms

    anark authored
  2. anark
Commits on Apr 11, 2013
  1. anark
This page is out of date. Refresh to see the latest.
52 app/assets/javascripts/s3_direct_upload.js.coffee
View
@@ -12,7 +12,7 @@ $.fn.S3Uploader = (options) ->
return this
- $uploadForm = this
+ $uploaderElement = this
settings =
path: ''
@@ -32,8 +32,9 @@ $.fn.S3Uploader = (options) ->
form.submit() for form in forms_for_submit
false
- setUploadForm = ->
- $uploadForm.fileupload
+ setUploadElement = ->
+ $uploaderElement.fileupload
+ url: settings.url if settings.url
add: (e, data) ->
file = data.files[0]
@@ -42,14 +43,14 @@ $.fn.S3Uploader = (options) ->
unless settings.before_add and not settings.before_add(file)
current_files.push data
data.context = $($.trim(tmpl("template-upload", file))) if $('#template-upload').length > 0
- $(data.context).appendTo(settings.progress_bar_target || $uploadForm)
+ $(data.context).appendTo(settings.progress_bar_target($(this)) || $uploaderElement)
if settings.click_submit_target
forms_for_submit.push data
else
data.submit()
start: (e) ->
- $uploadForm.trigger("s3_uploads_start", [e])
+ $uploaderElement.trigger("s3_uploads_start", [e])
progress: (e, data) ->
if data.context
@@ -57,38 +58,37 @@ $.fn.S3Uploader = (options) ->
data.context.find('.bar').css('width', progress + '%')
done: (e, data) ->
- content = build_content_object $uploadForm, data.files[0], data.result
+ content = build_content_object $uploaderElement, data.files[0], data.result
- to = $uploadForm.data('callback-url')
+ to = $uploaderElement.data('callback-url')
if to
- content[$uploadForm.data('callback-param')] = content.url
+ content[$uploaderElement.data('callback-param')] = content.url
+ element = $(this)
$.ajax
- type: $uploadForm.data('callback-method')
+ type: $uploaderElement.data('callback-method')
url: to
data: content
- beforeSend: ( xhr, settings ) -> $uploadForm.trigger( 'ajax:beforeSend', [xhr, settings] )
- complete: ( xhr, status ) -> $uploadForm.trigger( 'ajax:complete', [xhr, status] )
- success: ( data, status, xhr ) -> $uploadForm.trigger( 'ajax:success', [data, status, xhr] )
- error: ( xhr, status, error ) -> $uploadForm.trigger( 'ajax:error', [xhr, status, error] )
-
- # $.post(to, content)
+ beforeSend: ( xhr, settings ) -> element.trigger( 'ajax:beforeSend', [xhr, settings] )
+ complete: ( xhr, status ) -> element.trigger( 'ajax:complete', [xhr, status] )
+ success: ( data, status, xhr ) -> element.trigger( 'ajax:success', [data, status, xhr] )
+ error: ( xhr, status, error ) -> element.trigger( 'ajax:error', [xhr, status, error] )
data.context.remove() if data.context && settings.remove_completed_progress_bar # remove progress bar
- $uploadForm.trigger("s3_upload_complete", [content])
+ $uploaderElement.trigger("s3_upload_complete", [content])
current_files.splice($.inArray(data, current_files), 1) # remove that element from the array
- $uploadForm.trigger("s3_uploads_complete", [content]) unless current_files.length
+ $uploaderElement.trigger("s3_uploads_complete", [content]) unless current_files.length
fail: (e, data) ->
- content = build_content_object $uploadForm, data.files[0], data.result
+ content = build_content_object $uploaderElement, data.files[0], data.result
content.error_thrown = data.errorThrown
data.context.remove() if data.context && settings.remove_failed_progress_bar # remove progress bar
- $uploadForm.trigger("s3_upload_failed", [content])
+ $uploaderElement.trigger("s3_upload_failed", [content])
formData: (form) ->
- data = form.serializeArray()
+ data = $('.s3upload_hidden_fields').serializeArray()
fileType = ""
if "type" of @files[0]
fileType = @files[0].type
@@ -101,15 +101,15 @@ $.fn.S3Uploader = (options) ->
data[1].value = settings.path + key
data
- build_content_object = ($uploadForm, file, result) ->
+ build_content_object = ($uploaderElement, file, result) ->
content = {}
if result # Use the S3 response to set the URL to avoid character encodings bugs
content.url = $(result).find("Location").text()
content.filepath = $('<a />').attr('href', content.url)[0].pathname
- else # IE <= 9 return a null result object so we use the file object instead
- domain = $uploadForm.attr('action')
- content.filepath = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
- content.url = domain + content.filepath + '/' + encodeURIComponent(file.name)
+ #else # IE <= 9 return a null result object so we use the file object instead
+ #domain = $uploaderElement.attr('action')
Wayne Owner

These three lines seem to work with IE, did you have problems here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ #content.filepath = settings.path + $uploaderElement.find('input[name=key]').val().replace('/${filename}', '')
+ #content.url = domain + content.filepath + '/' + encodeURIComponent(file.name)
content.filename = file.name
content.filesize = file.size if 'size' of file
@@ -120,7 +120,7 @@ $.fn.S3Uploader = (options) ->
#public methods
@initialize = ->
- setUploadForm()
+ setUploadElement()
this
@path = (new_path) ->
39 lib/s3_direct_upload/form_helper.rb
View
@@ -4,11 +4,23 @@ def s3_uploader_form(options = {}, &block)
uploader = S3Uploader.new(options)
form_tag(uploader.url, uploader.form_options) do
uploader.fields.map do |name, value|
- hidden_field_tag(name, value)
+ hidden_field_tag(name, value, :class => "s3upload_hidden_fields")
end.join.html_safe + capture(&block)
end
end
+ def s3_uploader_hidden_fields(options = {}, &block)
+ uploader = S3Uploader.new(options)
+ (uploader.fields).map do |name, value|
+ hidden_field_tag(name, value, :class => "s3upload_hidden_fields")
+ end.join.html_safe
+ end
+
+ def s3_uploader_field(options = {})
+ uploader = S3Uploader.new(options)
+ file_field_tag(:file, uploader.field_options).html_safe
+ end
+
class S3Uploader
def initialize(options)
@key_starts_with = options[:key_starts_with] || "uploads/"
@@ -31,19 +43,33 @@ def initialize(options)
def form_options
{
- id: @options[:id],
- class: @options[:class],
method: "post",
authenticity_token: false,
multipart: true,
+ }.merge(field_options)
+ end
+
+ def field_options
+ form_data_options.merge(form_preset_options)
+ end
+
+ def form_data_options
+ {
data: {
- callback_url: @options[:callback_url],
- callback_method: @options[:callback_method],
- callback_param: @options[:callback_param]
+ callback_url: @options[:callback_url],
+ callback_method: @options[:callback_method],
+ callback_param: @options[:callback_param]
}.reverse_merge(@options[:data] || {})
}
end
+ def form_preset_options
+ {
+ id: @options[:id],
+ class: @options[:class],
+ }
+ end
+
def fields
{
:key => @options[:key] || key,
@@ -72,7 +98,6 @@ def policy_data
{
expiration: @options[:expiration],
conditions: [
- ["starts-with", "$utf8", ""],
Wayne Owner

Why was this removed? If you use a Rails' form_tag it will create a hidden input element with this attribute by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
["starts-with", "$key", @options[:key_starts_with]],
["starts-with", "$x-requested-with", ""],
["content-length-range", 0, @options[:max_file_size]],
Something went wrong with that request. Please try again.