Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ToDiscuss] Using $.widget ? #28

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 57 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ Add the following js and css to your asset pipeline:

**application.js.coffee**
```coffeescript
#= require jquery-fileupload/vendor/jquery.ui.widget
#= require jquery-fileupload/jquery.iframe-transport
#= require jquery-fileupload/jquery.fileupload
#= require s3_direct_upload
```

Expand All @@ -56,10 +59,10 @@ Create a new view that uses the form helper `s3_uploader_form`:
<% end %>
```

Then in your application.js.coffee, call the S3Uploader jQuery plugin on the element you created above:
Then in your application.js.coffee, call the s3upload jQuery plugin on the element you created above:
```coffeescript
jQuery ->
$("#myS3Uploader").S3Uploader()
$("#myS3Uploader").s3upload()
```

Optionally, you can also place this template in the same view for the progress bars:
Expand Down Expand Up @@ -119,65 +122,91 @@ Use the javascript in `s3_direct_upload` as a guide.
* `additional_data:` You can send additional data to your rails app in the persistence POST request. This would be accessable in your params hash as `params[:key][:value]`
Example: `{key: value}`
* `remove_completed_progress_bar:` By default, the progress bar will be removed once the file has been successfully uploaded. You can set this to `false` if you want to keep the progress bar.
* `before_add:` Callback function that executes before a file is added to the queue. It is passed file object and expects `true` or `false` to be returned. This could be useful if you would like to validate the filenames of files to be uploaded for example. If true is returned file will be uploaded as normal, false will cancel the upload.
* Supported callbacks (see below)

### Example with all options.
### Example with all options
```coffeescript
jQuery ->
$("#myS3Uploader").S3Uploader
$("#myS3Uploader").s3upload
path: 'path/to/my/files/on/s3'
additional_data: {key: 'value'}
remove_completed_progress_bar: false
before_add: myCallBackFunction() # must return true or false if set
before_add: (e, data) ->
return false unless (/(\.|\/)jpg$/i).test(data.file.type)
start: (e, data) -> // Do sth
done: (e, data) -> // Do sth
fail: (e, data) -> // Do sth

```

### Public methods
You can change the settings on your form later on by accessing the jQuery instance:
### Change options
You can change the options on your form by using the `option` method:

```coffeescript
jQuery ->
v = $("#myS3Uploader").S3Uploader()
...
v.path("new/path/")
v.additional_data("newdata")
$("#myS3Uploader").s3upload("option", "additional_data", {foo: "bar"})
$("#myS3Uploader").s3upload("option", "path", "new/path/")
```

### Javascript Event Hooks

The S3 upload widget provides two ways to use callback hooks:
```coffeescript
$('#myS3Uploader').s3upload(
start: ->
alert("start 1")
).on("s3uploadstart", ->
alert("start 2")
)
```

#### File added
`before_add` is fired before a file is added to the queue. It passes the file object through `data.file` and expects `true` or `false` to be returned. This could be useful if you would like to validate the filenames of files to be uploaded for example. If true is returned file will be uploaded as normal, false will cancel the upload.
```coffeescript
$('#myS3Uploader').on 's3uploadbefore_add', (e, data) ->
alert("Uploads have started")
```

### Javascript Events Hooks
#### Progress
During the upload process the `progress` event is fired. The callback receives a `data` object with the following attributes:

* `loaded`: Number of bytes already loaded
* `total`: Number of total bytes
* `percentage`: Percentage of loaded bytes

```coffeescript
$('#myS3Uploader').on 's3uploadprogress', (e, data) ->
$(".bar").css("width", "#{data.percentage}%")
```

#### First upload started
`s3_uploads_start` is fired once when any batch of uploads is starting.
`start` is fired once when any batch of uploads is starting.
```coffeescript
$('#myS3Uploader').bind 's3_uploads_start', (e) ->
$('#myS3Uploader').on 's3uploadstart', (e) ->
alert("Uploads have started")
```

#### Successfull upload
When a file has been successfully to S3, the `s3_upload_complete` is triggered on the form. A `content` object is passed along with the following attributes :
When a file has been successfully to S3, the `done` event is triggered on the form. A `data` object is passed along with the following attributes :

* `url` The full URL to the uploaded file on S3.
* `filename` The original name of the uploaded file.
* `filepath` The path to the file (without the filename or domain)
* `filesize` The size of the uploaded file.
* `filetype` The type of the uploaded file.
* `filekey` The S3 key of the uploaded file ("#{filepath}/#{filename}").

This hook could be used for example to fill a form hidden field with the returned S3 url :
```coffeescript
$('#myS3Uploader').bind "s3_upload_complete", (e, content) ->
$('#someHiddenField').val(content.url)
$('#myS3Uploader').on "s3uploaddone", (e, data) ->
$('#someHiddenField').val(data.url)
```

#### Failed upload
When an error occured during the transferm the `s3_upload_failed` is triggered on the form with the same `content` object is passed for the successful upload with the addition of the `error_thrown` attribute. The most basic way to handle this error would be to display an alert message to the user in case the upload fails :
```coffeescript
$('#myS3Uploader').bind "s3_upload_failed", (e, content) ->
alert("#{content.filename} failed to upload : #{content.error_thrown}")
```

#### All uploads completed
When all uploads finish in a batch an `s3_uploads_complete` event will be triggered on `document`, so you could do something like:
When an error occured during the transferm the `fail` is triggered on the form with the same `data` object is passed for the successful upload with the addition of the `error_thrown` attribute. The most basic way to handle this error would be to display an alert message to the user in case the upload fails :
```coffeescript
$(document).bind 's3_uploads_complete', ->
alert("All Uploads completed")
$('#myS3Uploader').on "s3uploadfail", (e, data) ->
alert("#{data.filename} failed to upload : #{data.error_thrown}")
```

## Cleaning old uploads on S3
Expand Down
199 changes: 91 additions & 108 deletions app/assets/javascripts/s3_direct_upload.js.coffee
Original file line number Diff line number Diff line change
@@ -1,108 +1,91 @@
#= require jquery-fileupload/basic
#= require jquery-fileupload/vendor/tmpl

$ = jQuery

$.fn.S3Uploader = (options) ->

# support multiple elements
if @length > 1
@each ->
$(this).S3Uploader options

return this

$uploadForm = this

settings =
path: ''
additional_data: null
before_add: null
remove_completed_progress_bar: true

$.extend settings, options

current_files = []

setUploadForm = ->
$uploadForm.fileupload

add: (e, data) ->
current_files.push data
file = data.files[0]
unless settings.before_add and not settings.before_add(file)
data.context = $(tmpl("template-upload", file)) if $('#template-upload').length > 0
$uploadForm.append(data.context)
data.submit()

start: (e) ->
$uploadForm.trigger("s3_uploads_start", [e])

progress: (e, data) ->
if data.context
progress = parseInt(data.loaded / data.total * 100, 10)
data.context.find('.bar').css('width', progress + '%')

done: (e, data) ->
content = build_content_object $uploadForm, data.files[0], data.result

to = $uploadForm.data('post')
if to
content[$uploadForm.data('as')] = content.url
$.post(to, content)

data.context.remove() if data.context && settings.remove_completed_progress_bar # remove progress bar

current_files.splice($.inArray(data, current_files), 1) # remove that element from the array
$uploadForm.trigger("s3_uploads_complete", [content]) unless current_files.length

fail: (e, data) ->
content = build_content_object $uploadForm, data.files[0], data.result
content.error_thrown = data.errorThrown
$uploadForm.trigger("s3_upload_failed", [content])

formData: (form) ->
data = form.serializeArray()
fileType = ""
if "type" of @files[0]
fileType = @files[0].type
data.push
name: "Content-Type"
value: fileType

data[1].value = settings.path + data[1].value

data

build_content_object = ($uploadForm, file, result) ->
domain = $uploadForm.attr('action')
content = {}
if result # Use the S3 response to set the URL to avoid character encodings bugs
path = $('Key', result).text()
split_path = path.split('/')
content.url = domain + path
content.filename = split_path[split_path.length - 1]
content.filepath = split_path.slice(0, split_path.length - 1).join('/')
else # IE8 and IE9 return a null result object so we use the file object instead
path = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
content.url = domain + path + '/' + file.name
content.filename = file.name
content.filepath = path

content.filesize = file.size if 'size' of file
content.filetype = file.type if 'type' of file
content = $.extend content, settings.additional_data if settings.additional_data
content

#public methods
@initialize = ->
setUploadForm()
this

@path = (new_path) ->
settings.path = new_path

@additional_data = (new_data) ->
settings.additional_data = new_data

@initialize()
(($) ->
$.widget("waynehoover.s3upload", {
options: {
path: ''
additional_data: null
remove_completed_progress_bar: true

before_add: null
start: null
}
_create: ->
@current_files = []
@form = $(this.element.prop("form"))
that = @

@element.fileupload
add: (e, data) =>
@current_files.push data
file = data.files[0]
if @_trigger("before_add", null, {file: file})
data.context = $(tmpl("template-upload", file)) if $('#template-upload').length > 0
@form.append(data.context)
data.submit()

start: (e) =>
@_trigger("start")

progress: (e, data) =>
data = $.extend({}, data, {percentage: parseInt(data.loaded / data.total * 100, 10)})
data.context.find('.bar').css('width', data.percentage + '%') if data.context
@_trigger("progress", e, data)

done: (e, data) =>
content = @_buildContentObject data.files[0], data.result

to = @form.data('post')
if to
content[@form.data('as')] = content.url
$.post(to, content)

data.context.remove() if data.context && @options.remove_completed_progress_bar # remove progress bar

@current_files.splice($.inArray(data, @current_files), 1) # remove that element from the array
@_trigger("done", e, content) unless @current_files.length

fail: (e, data) =>
content = @_buildContentObject data.files[0], data.result
content.error_thrown = data.errorThrown
@_trigger("fail", e, content)

formData: (form) ->
data = form.serializeArray()
fileType = ""
if "type" of @files[0]
fileType = @files[0].type
data.push
name: "Content-Type"
value: fileType

data[1].value = that.options.path + data[1].value

data

_buildContentObject: (file, result) ->
domain = @form.attr('action')
content = {}
if result # Use the S3 response to set the URL to avoid character encodings bugs
path = $('Key', result).text()
split_path = path.split('/')
content.url = domain + path
content.filename = split_path[split_path.length - 1]
content.filepath = split_path.slice(0, split_path.length - 1).join('/')
else # IE8 and IE9 return a null result object so we use the file object instead
path = @options.path + @form.find('input[name=key]').val().replace('/${filename}', '')
content.url = domain + path + '/' + file.name
content.filename = file.name
content.filepath = path

content.filekey = "#{content.filepath}/#{content.filename}"
content.filesize = file.size if 'size' of file
content.filetype = file.type if 'type' of file
content = $.extend content, @options.additional_data if @options.additional_data
content

path: (new_path) ->
@options.path = new_path

additional_data: (new_data) ->
@options.additional_data = new_data
})
)(jQuery)