This repository has been archived by the owner on Nov 11, 2017. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add core files and finalize core functionality
- Loading branch information
1 parent
8ccc719
commit d30ad76
Showing
10 changed files
with
319 additions
and
1 deletion.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
//= require jack_up/base | ||
//= require jack_up/events | ||
//= require_tree ./jack_up |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
class @JackUp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
$.fn.jackUpAjax = (processor) -> | ||
$(@).change(processor.processFilesForEvent) | ||
@ | ||
|
||
$.fn.jackUpDragAndDrop = (processor) -> | ||
new JackUp.DragAndDrop($(@), processor) | ||
@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |