Skip to content
This repository has been archived by the owner on Nov 11, 2017. It is now read-only.

Commit

Permalink
Add core files and finalize core functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuaclayton committed Aug 3, 2012
1 parent 8ccc719 commit d30ad76
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 1 deletion.
File renamed without changes.
180 changes: 180 additions & 0 deletions README.md
@@ -1,3 +1,183 @@
# JackUp

## Easy AJAX file uploading in Rails.

### Install

Modify your `Gemfile`:

```ruby
gem 'jack_up'
```

and run `bundle install` from your shell.

Modify your `application.js` manifest:

```javascript
//= require jquery
//= require underscore
//= require jack_up
//= require_tree .
```

### Requirements

Rails 3.1 (for the asset pipeline), CoffeeScript, and both jQuery and
Underscore.js included in your `application.js` manifest.

### Usage

Create a JackUp.Processor, binding to various events emitted.

```coffeescript
$ -> # when the document is ready
# create a new processor with the endpoint to where your assets are uploaded
jackUp = new JackUp.Processor(path: '/assets')

# called if upload is an image; returns an image jQuery object with src attribute assigned
jackUp.on 'upload:imageRenderReady', (e, options) ->
# assigns a data-attribute with the file guid for later referencing
# set the border color to red, denoting that the image is still being uploaded
options.image.attr("data-id", options.file.__guid__).css(border: "5px solid red")
$('.file-drop').append(options.image)

# upload has been sent to server; server will handle processing
jackUp.on "upload:sentToServer", (e, options) ->
# change the border color to yellow to signify successful upload (server is still processing)
$("img[data-id='#{options.file.__guid__}']").css borderColor: 'yellow'

# when server responds successfully
jackUp.on "upload:success", (e, options) ->
# server has completed processing the image and has returned a response
$("img[data-id='#{options.file.__guid__}']").css(borderColor: "green")

# when server returns a non-200 response
jackUp.on "upload:failure", (e, options) ->
# alert the file name
alert("'#{options.file.name}' upload failed; please retry")
# remove the image from the dom since the upload failed
$("img[data-id='#{options.file.__guid__}']").remove()
```

Once the processor is set up, wire up drag-and-drop support:

```coffeescript
$('.file-drop').jackUpDragAndDrop(jackUp)
```

If you just want to bind to a standard `<input type='file'>`:

```coffeescript
$('.standard-attachment').jackUpAjax(jackUp)
```

You can use both at the same time, referencing the same `JackUp.Processor`, in
order to provide both options to your users.

### Example Rails Setup

For instant file uploading:

```ruby
# Gemfile
gem 'rails'
gem 'paperclip'
gem 'rack-raw-upload'
```

Using the `rack-raw-upload` gem allows for accessing the file posted to the
controller via `params[:file]`; this makes it incredibly easy to handle file
uploads.

```ruby
# app/models/asset.rb
class Asset < ActiveRecord::Base
has_attached_file :photo
attr_accessible :photo
end

# app/controllers/assets_controller.rb
class AssetsController < ApplicationController
def create
@asset = Asset.new(photo: params[:file])

if @asset.save
render json: @asset
else
head :bad_request
end
end
end
```

This view code could be placed anywhere for immediate uploading:

```haml
.file-drop
%span{ 'data-placeholder' => 'Drop files here' } Drop files here
%input.standard-attachment{ name: 'standard_attachment', accept: 'image/*', type: :file, multiple: :multiple }
```

If attaching assets to a different model, additionally use:

```ruby
# app/models/post.rb
class Post < ActiveRecord::Base
has_many :assets, dependent: :destroy

attr_accessible :asset_ids, :assets_attributes
accepts_nested_attributes_for :assets
end

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def new
@post = Post.new
@post.assets.build
end

def create
@post = Post.new(params[:post])
@post.save
respond_with @post
end
end
```

To wire up the posts view:

```haml
# app/views/posts/new.html.haml
= form_for @post, html: { multipart: true } do |form|
= form.text_field :title, { placeholder: 'Title' }
.file-drop
%span{ 'data-placeholder' => 'Drop files here' } Drop files here
%input.standard-attachment{ name: 'standard_attachment', accept: "image/*", type: :file, multiple: :multiple }
= form.submit 'Create Post'
```

```coffeescript
# app/assets/javascripts/posts.coffee
# truncated from above to demonstrate additional code to associate uploads
# with posts
jackUp.on "upload:success", (e, options) ->
$("img[data-id='#{options.file.__guid__}']").css(borderColor: "green")

# read the response from the server
asset = JSON.parse(options.responseText)
assetId = asset.id
# create a hidden input containing the asset id of the uploaded file
assetIdsElement = $("<input type='hidden' name='post[asset_ids][]'>").val(assetId)
# append it to the form so saving the form associates the created post
# with the uploaded assets
$(".file-drop").parent("form").append(assetIdsElement)
```

## License

JackUp is copyright 2012 Josh Steiner, Josh Clayton, and thoughtbot, inc., and may be redistributed under the terms specified in the LICENSE file.
2 changes: 1 addition & 1 deletion jack_up.gemspec
Expand Up @@ -13,5 +13,5 @@ Gem::Specification.new do |s|
s.summary = 'Easy AJAX file uploading in Rails'
s.description = 'Easy AJAX file uploading in Rails'
s.files = Dir['lib/**/*'] + ['MIT-LICENSE', 'Rakefile', 'README.rdoc']
s.add_dependency 'rails', '~> 3.2.7'
s.add_dependency 'rails', '~> 3.1'
end
3 changes: 3 additions & 0 deletions lib/assets/javascripts/jack_up.js
@@ -0,0 +1,3 @@
//= require jack_up/base
//= require jack_up/events
//= require_tree ./jack_up
1 change: 1 addition & 0 deletions lib/assets/javascripts/jack_up/base.coffee
@@ -0,0 +1 @@
class @JackUp
24 changes: 24 additions & 0 deletions lib/assets/javascripts/jack_up/drag_and_drop.coffee
@@ -0,0 +1,24 @@
ignoreEvent = (event) ->
event.stopPropagation()
event.preventDefault()

class @JackUp.DragAndDrop
constructor: (@droppableElement, @processor) ->
@droppableElement
.bind("dragenter", @_drag)
.bind("drop", @_drop)
.bind("drop", @_dragOut)

_drag: (event) =>
ignoreEvent event
event.originalEvent.dataTransfer.dropEffect = "copy"
@droppableElement.addClass("hover")

_dragOut: (event) =>
ignoreEvent event
@droppableElement.removeClass("hover")

_drop: (event) =>
ignoreEvent event
@droppableElement.find('[data-placeholder]').hide()
@processor.processFilesForEvent(event)
11 changes: 11 additions & 0 deletions lib/assets/javascripts/jack_up/events.coffee
@@ -0,0 +1,11 @@
@JackUp.Events =
trigger: ->
$(@).trigger arguments...

on: ->
$(@).bind arguments...

bubble: (eventList..., options) ->
_.each eventList, (eventName) =>
options.from.on eventName, =>
@trigger eventName, arguments[1]
54 changes: 54 additions & 0 deletions lib/assets/javascripts/jack_up/file_uploader.coffee
@@ -0,0 +1,54 @@
railsCSRFData = ->
csrfParam = $('meta[name=csrf-param]').attr('content')
csrfToken = $('meta[name=csrf-token]').attr('content')

formData = {}
formData[csrfParam] = csrfToken
JSON.stringify formData

class @JackUp.FileUploader
constructor: (@options) ->
@path = @options.path
@responded = false

_onProgressHandler: (file) =>
(progress) =>
if progress.lengthComputable
percent = progress.loaded/progress.total*100
@trigger 'upload:percentComplete', percentComplete: percent, progress: progress

if percent == 100
@trigger 'upload:sentToServer', file: file

_onReadyStateChangeHandler: (file) =>
(event) =>
status = null
return if event.target.readyState != 4

try
status = event.target.status
catch error
return

if status > 0 && status != 200
@trigger 'upload:failure', responseText: event.target.responseText, event: event, file: file

if status == 200 && event.target.responseText && !@responded
@responded = true
@trigger 'upload:success', responseText: event.target.responseText, event: event, file: file

upload: (file) ->
xhr = new XMLHttpRequest()
xhr.upload.addEventListener 'progress', @_onProgressHandler(file), false
xhr.addEventListener 'readystatechange', @_onReadyStateChangeHandler(file), false

xhr.open 'POST', @path, true

xhr.setRequestHeader 'Content-Type', file.type
xhr.setRequestHeader 'X-File-Name', file.name
xhr.setRequestHeader 'X-Query-Params', railsCSRFData()

@trigger 'upload:start', file: file
xhr.send file

_.extend JackUp.FileUploader.prototype, JackUp.Events
7 changes: 7 additions & 0 deletions lib/assets/javascripts/jack_up/jquery.coffee
@@ -0,0 +1,7 @@
$.fn.jackUpAjax = (processor) ->
$(@).change(processor.processFilesForEvent)
@

$.fn.jackUpDragAndDrop = (processor) ->
new JackUp.DragAndDrop($(@), processor)
@
38 changes: 38 additions & 0 deletions lib/assets/javascripts/jack_up/processor.coffee
@@ -0,0 +1,38 @@
getFilesFromEvent = (event) ->
if event.originalEvent.dataTransfer?
event.originalEvent.dataTransfer.files
else if event.originalEvent.currentTarget? && event.originalEvent.currentTarget.files?
event.originalEvent.currentTarget.files
else if event.originalEvent.target? && event.originalEvent.target.files?
event.originalEvent.target.files
else
[]

filesWithData = (event) ->
_.map getFilesFromEvent(event), (file) ->
file.__guid__ = Math.random().toString(36)
file

class @JackUp.Processor
constructor: (options) ->
@uploadPath = options.path

processFilesForEvent: (event) =>
_.each filesWithData(event), (file) =>
reader = new FileReader()
reader.onload = (event) =>
@trigger 'upload:dataRenderReady', result: event.target.result, file: file

if /^data:image/.test event.target.result
image = $("<img>").attr("src", event.target.result)
@trigger 'upload:imageRenderReady', image: image, file: file

reader.readAsDataURL(file)

fileUploader = new JackUp.FileUploader(path: @uploadPath)
@bubble 'upload:success', 'upload:failure', 'upload:sentToServer',
from: fileUploader

fileUploader.upload file

_.extend JackUp.Processor.prototype, JackUp.Events

0 comments on commit d30ad76

Please sign in to comment.