Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add core files and finalize core functionality

  • Loading branch information...
commit d30ad76fc91003ac7daf0414132e7e34e9ba50f1 1 parent 8ccc719
@joshuaclayton joshuaclayton authored
View
0  MIT-LICENSE → LICENSE
File renamed without changes
View
180 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.
View
2  jack_up.gemspec
@@ -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
View
3  lib/assets/javascripts/jack_up.js
@@ -0,0 +1,3 @@
+//= require jack_up/base
+//= require jack_up/events
+//= require_tree ./jack_up
View
1  lib/assets/javascripts/jack_up/base.coffee
@@ -0,0 +1 @@
+class @JackUp
View
24 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)
View
11 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]
View
54 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
View
7 lib/assets/javascripts/jack_up/jquery.coffee
@@ -0,0 +1,7 @@
+$.fn.jackUpAjax = (processor) ->
+ $(@).change(processor.processFilesForEvent)
+ @
+
+$.fn.jackUpDragAndDrop = (processor) ->
+ new JackUp.DragAndDrop($(@), processor)
+ @
View
38 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
Please sign in to comment.
Something went wrong with that request. Please try again.