diff --git a/Gemfile b/Gemfile
index 4f978d144c6a3d1217118b6cbfeb5c56360622da..ed7eea7a6c80a4a6359519f5d79240c292516055 100644
--- a/Gemfile
+++ b/Gemfile
@@ -17,6 +17,7 @@ gem 'rack-cors', '~> 0.2.4', :require => 'rack/cors'
 gem 'devise', '1.5.3'
 gem 'jwt'
 gem 'oauth2-provider', '0.0.19'
+gem 'remotipart', '~> 1.0'
 
 gem 'omniauth', '1.0.1'
 gem 'omniauth-facebook'
diff --git a/Gemfile.lock b/Gemfile.lock
index 32c069695cf6c207fe4093e9ded87d166a240d8b..98f6dc1e771782cd975ada1e1f1440b21ccc4e7f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -162,7 +162,7 @@ GEM
     fixture_builder (0.3.1)
       activerecord (>= 2)
       activesupport (>= 2)
-    fog (1.2.0)
+    fog (1.3.0)
       builder
       excon (~> 0.12.0)
       formatador (~> 0.2.0)
@@ -341,6 +341,7 @@ GEM
     redis (2.2.2)
     redis-namespace (1.0.3)
       redis (< 3.0.0)
+    remotipart (1.0.2)
     resque (1.20.0)
       multi_json (~> 1.0)
       redis-namespace (~> 1.0.2)
@@ -513,6 +514,7 @@ DEPENDENCIES
   rails-i18n
   rails_autolink
   redcarpet (= 2.0.1)
+  remotipart (~> 1.0)
   resque (= 1.20.0)
   resque-timeout (= 1.0.0)
   rest-client (= 1.6.7)
diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb
index c2d1978da9acb6a8b906a6a1156f7dbe5c77d51c..30556bfc619863c4e24cc1c70624efcff037fc46 100644
--- a/app/controllers/photos_controller.rb
+++ b/app/controllers/photos_controller.rb
@@ -41,21 +41,23 @@ class PhotosController < ApplicationController
   end
 
   def create
-    begin
-      raise unless params[:photo][:aspect_ids]
-
-      if params[:photo][:aspect_ids] == "all"
-        params[:photo][:aspect_ids] = current_user.aspects.collect{|x| x.id}
-      elsif params[:photo][:aspect_ids].is_a?(Hash)
-        params[:photo][:aspect_ids] = params[:photo][:aspect_ids].values
-      end
+    rescuing_photo_errors do |p|
+      if remotipart_submitted?
+         @photo = current_user.build_post(:photo, params[:photo])
+      else
+        raise "not remotipart" unless params[:photo][:aspect_ids]
 
-      params[:photo][:user_file] = file_handler(params)
+        if params[:photo][:aspect_ids] == "all"
+          params[:photo][:aspect_ids] = current_user.aspects.collect { |x| x.id }
+        elsif params[:photo][:aspect_ids].is_a?(Hash)
+          params[:photo][:aspect_ids] = params[:photo][:aspect_ids].values
+        end
 
-      @photo = current_user.build_post(:photo, params[:photo])
+        params[:photo][:user_file] = file_handler(params)
 
-      if @photo.save
+        @photo = current_user.build_post(:photo, params[:photo])
 
+        if @photo.save
         aspects = current_user.aspects_from_ids(params[:photo][:aspect_ids])
 
         unless @photo.pending
@@ -65,11 +67,14 @@ class PhotosController < ApplicationController
 
         if params[:photo][:set_profile_photo]
           profile_params = {:image_url => @photo.url(:thumb_large),
-                           :image_url_medium => @photo.url(:thumb_medium),
-                           :image_url_small => @photo.url(:thumb_small)}
+                            :image_url_medium => @photo.url(:thumb_medium),
+                            :image_url_small => @photo.url(:thumb_small)}
           current_user.update_profile(profile_params)
         end
+        end
+      end
 
+      if @photo.save
         respond_to do |format|
           format.json{ render(:layout => false , :json => {"success" => true, "data" => @photo}.to_json )}
           format.html{ render(:layout => false , :json => {"success" => true, "data" => @photo}.to_json )}
@@ -77,19 +82,6 @@ class PhotosController < ApplicationController
       else
         respond_with @photo, :location => photos_path, :error => message
       end
-
-    rescue TypeError
-      message = I18n.t 'photos.create.type_error'
-      respond_with @photo, :location => photos_path, :error => message
-
-    rescue CarrierWave::IntegrityError
-      message = I18n.t 'photos.create.integrity_error'
-      respond_with @photo, :location => photos_path, :error => message
-
-    rescue RuntimeError => e
-      message = I18n.t 'photos.create.runtime_error'
-      respond_with @photo, :location => photos_path, :error => message
-      raise e
     end
   end
 
@@ -200,4 +192,23 @@ class PhotosController < ApplicationController
       file
     end
   end
+
+
+  def rescuing_photo_errors
+    begin
+      yield
+    rescue TypeError
+      message = I18n.t 'photos.create.type_error'
+      respond_with @photo, :location => photos_path, :error => message
+
+    rescue CarrierWave::IntegrityError
+      message = I18n.t 'photos.create.integrity_error'
+      respond_with @photo, :location => photos_path, :error => message
+
+    rescue RuntimeError => e
+      message = I18n.t 'photos.create.runtime_error'
+      respond_with @photo, :location => photos_path, :error => message
+      raise e
+    end
+  end
 end
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 7fff7c82edc5c1eef2a1b5918206acd8249dd7ae..e4cb4b10462fe486821f67454b20bf3103ecf99d 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -16,7 +16,7 @@ class PostsController < ApplicationController
              :xml
 
   def new
-    render :text => "", :layout => true
+
   end
 
   def show
@@ -40,7 +40,7 @@ class PostsController < ApplicationController
         format.xml{ render :xml => @post.to_diaspora_xml }
         format.mobile{render 'posts/show.mobile.haml'}
         format.json{ render :json => PostPresenter.new(@post, current_user).to_json }
-        format.any{render 'posts/show.html.haml'}
+         format.any{render 'posts/show.html.haml'}
       end
 
     else
diff --git a/app/controllers/status_messages_controller.rb b/app/controllers/status_messages_controller.rb
index 5953993a925886268f86e03d565d8ec4bdac76da..b6764b890a61c456607587c7b1635797e44c5878 100644
--- a/app/controllers/status_messages_controller.rb
+++ b/app/controllers/status_messages_controller.rb
@@ -53,7 +53,8 @@ class StatusMessagesController < ApplicationController
       receiving_services = Service.titles(services)
 
       current_user.dispatch_post(@status_message, :url => short_post_url(@status_message.guid), :service_types => receiving_services)
-      
+
+      #this is done implicitly, somewhere else, apparently, says max. :'(
       # @status_message.photos.each do |photo|
       #   current_user.dispatch_post(photo)
       # end
diff --git a/app/views/photos/_new_photo.haml b/app/views/photos/_new_photo.haml
index 4da494f18861e0c3a665b7c713bf02794a0c5df7..95fa60e27b5fa8a9308ee2ab80df1d41ccf6c6b7 100644
--- a/app/views/photos/_new_photo.haml
+++ b/app/views/photos/_new_photo.haml
@@ -83,4 +83,4 @@
    });
  }
 
- createUploader();
+ createUploader();
\ No newline at end of file
diff --git a/app/views/posts/new.html.haml b/app/views/posts/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a20a287a24b03b6c48b7ca87ec401dfd068d35cb
--- /dev/null
+++ b/app/views/posts/new.html.haml
@@ -0,0 +1,15 @@
+= form_for Photo.new, :html => { :multipart => true }, :remote => true do |f|
+  = f.label :user_file
+  = f.file_field :user_file
+  = f.submit
+
+:javascript
+  $(function(){
+    console.log($('#new_photo'))
+    $('#new_photo').bind('ajax:success', function(event, data) {
+      alert("happy day")
+      console.log(data)
+      console.log(new Backbone.Model(data)); // Your newly created Backbone.js model
+
+    });
+  });
\ No newline at end of file
diff --git a/config/assets.yml b/config/assets.yml
index 5212580a3f517eacf6fbd6f3e1642499b7f0afc5..e7086b92b16730b5c9a8c798752f43fed64f82cc 100644
--- a/config/assets.yml
+++ b/config/assets.yml
@@ -25,6 +25,8 @@ javascripts:
     - public/javascripts/vendor/timeago.js
     - public/javascripts/vendor/facebox.js
     - public/javascripts/vendor/underscore.js
+    - public/javascripts/vendor/jquery.iframe-transport.js
+    - public/javascripts/vendor/jquery.remotipart.js
     - public/javascripts/vendor/jquery.events.input.js
     - public/javascripts/vendor/jquery.elastic.js
     - public/javascripts/vendor/jquery.mentionsInput.js
diff --git a/public/javascripts/rails.js b/public/javascripts/rails.js
index 2fbb9b83c06cc9339d4a82d28c02cb23a1fd41b3..028b0fc62b03ad387c76651faf114255c13b52ec 100644
--- a/public/javascripts/rails.js
+++ b/public/javascripts/rails.js
@@ -1,198 +1,374 @@
-/* Clear form plugin - called using $("elem").clearForm(); */
-$.fn.clearForm = function() {
-  return this.each(function() {
-    if ($(this).is('form')) {
-      return $(':input', this).clearForm();
-    }
-    if ($(this).hasClass('clear_on_submit') || $(this).is(':text') || $(this).is(':password') || $(this).is('textarea')) {
-      $(this).val('');
-    } else if ($(this).is(':checkbox') || $(this).is(':radio')) {
-      $(this).attr('checked', false);
-    } else if ($(this).is('select')) {
-      this.selectedIndex = -1;
-    } else if ($(this).attr('name') == 'photos[]') {
-      $(this).val('');
-    }
-    $(this).blur();
-  });
-};
+(function($, undefined) {
 
 /**
  * Unobtrusive scripting adapter for jQuery
  *
- * Requires jQuery 1.4.3 or later.
+ * Requires jQuery 1.6.0 or later.
  * https://github.com/rails/jquery-ujs
+
+ * Uploading file using rails.js
+ * =============================
+ *
+ * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
+ * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
+ *
+ * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
+ *
+ * Ex:
+ *     $('form').live('ajax:aborted:file', function(event, elements){
+ *       // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
+ *       // Returning false in this handler tells rails.js to disallow standard form submission
+ *       return false;
+ *     });
+ *
+ * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
+ *
+ * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
+ * techniques like the iframe method to upload the file instead.
+ *
+ * Required fields in rails.js
+ * ===========================
+ *
+ * If any blank required inputs (required="required") are detected in the remote form, the whole form submission
+ * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
+ *
+ * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
+ *
+ * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
+ *    get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
+ *
+ * Ex:
+ *     $('form').live('ajax:aborted:required', function(event, elements){
+ *       // Returning false in this handler tells rails.js to submit the form anyway.
+ *       // The blank required inputs are passed to this function in `elements`.
+ *       return ! confirm("Would you like to submit the form with missing info?");
+ *     });
  */
 
-(function($) {
-  // Make sure that every Ajax request sends the CSRF token
-  function CSRFProtection(fn) {
-    var token = $('meta[name="csrf-token"]').attr('content');
-    if (token) fn(function(xhr) { xhr.setRequestHeader('X-CSRF-Token', token) });
-  }
-  if ($().jquery == '1.5') { // gruesome hack
-    var factory = $.ajaxSettings.xhr;
-    $.ajaxSettings.xhr = function() {
-      var xhr = factory();
-      CSRFProtection(function(setHeader) {
-        var open = xhr.open;
-        xhr.open = function() { open.apply(this, arguments); setHeader(this) };
+  // Shorthand to make it a little easier to call public rails functions from within rails.js
+  var rails;
+
+  $.rails = rails = {
+    // Link elements bound by jquery-ujs
+    linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',
+
+    // Select elements bound by jquery-ujs
+    inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
+
+    // Form elements bound by jquery-ujs
+    formSubmitSelector: 'form',
+
+    // Form input elements bound by jquery-ujs
+    formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not(button[type])',
+
+    // Form input elements disabled during form submission
+    disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
+
+    // Form input elements re-enabled after form submission
+    enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
+
+    // Form required input elements
+    requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',
+
+    // Form file input elements
+    fileInputSelector: 'input:file',
+
+    // Link onClick disable selector with possible reenable after remote submission
+    linkDisableSelector: 'a[data-disable-with]',
+
+    // Make sure that every Ajax request sends the CSRF token
+    CSRFProtection: function(xhr) {
+      var token = $('meta[name="csrf-token"]').attr('content');
+      if (token) xhr.setRequestHeader('X-CSRF-Token', token);
+    },
+
+    // Triggers an event on an element and returns false if the event result is false
+    fire: function(obj, name, data) {
+      var event = $.Event(name);
+      obj.trigger(event, data);
+      return event.result !== false;
+    },
+
+    // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
+    confirm: function(message) {
+      return confirm(message);
+    },
+
+    // Default ajax function, may be overridden with custom function in $.rails.ajax
+    ajax: function(options) {
+      return $.ajax(options);
+    },
+
+    // Submits "remote" forms and links with ajax
+    handleRemote: function(element) {
+      var method, url, data,
+        crossDomain = element.data('cross-domain') || null,
+        dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType),
+        options;
+
+      if (rails.fire(element, 'ajax:before')) {
+
+        if (element.is('form')) {
+          method = element.attr('method');
+          url = element.attr('action');
+          data = element.serializeArray();
+          // memoized value from clicked submit button
+          var button = element.data('ujs:submit-button');
+          if (button) {
+            data.push(button);
+            element.data('ujs:submit-button', null);
+          }
+        } else if (element.is(rails.inputChangeSelector)) {
+          method = element.data('method');
+          url = element.data('url');
+          data = element.serialize();
+          if (element.data('params')) data = data + "&" + element.data('params');
+        } else {
+          method = element.data('method');
+          url = element.attr('href');
+          data = element.data('params') || null;
+        }
+
+        options = {
+          type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain,
+          // stopping the "ajax:beforeSend" event will cancel the ajax request
+          beforeSend: function(xhr, settings) {
+            if (settings.dataType === undefined) {
+              xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
+            }
+            return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
+          },
+          success: function(data, status, xhr) {
+            alert("hella boner jamz")
+            element.trigger('ajax:success', [data, status, xhr]);
+          },
+          complete: function(xhr, status) {
+            element.trigger('ajax:complete', [xhr, status]);
+          },
+          error: function(xhr, status, error) {
+            element.trigger('ajax:error', [xhr, status, error]);
+          }
+        };
+        // Only pass url to `ajax` options if not blank
+        if (url) { options.url = url; }
+
+        return rails.ajax(options);
+      } else {
+        return false;
+      }
+    },
+
+    // Handles "data-method" on links such as:
+    // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
+    handleMethod: function(link) {
+      var href = link.attr('href'),
+        method = link.data('method'),
+        target = link.attr('target'),
+        csrf_token = $('meta[name=csrf-token]').attr('content'),
+        csrf_param = $('meta[name=csrf-param]').attr('content'),
+        form = $('<form method="post" action="' + href + '"></form>'),
+        metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
+
+      if (csrf_param !== undefined && csrf_token !== undefined) {
+        metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
+      }
+
+      if (target) { form.attr('target', target); }
+
+      form.hide().append(metadata_input).appendTo('body');
+      form.submit();
+    },
+
+    /* Disables form elements:
+      - Caches element value in 'ujs:enable-with' data store
+      - Replaces element text with value of 'data-disable-with' attribute
+      - Sets disabled property to true
+    */
+    disableFormElements: function(form) {
+      form.find(rails.disableSelector).each(function() {
+        var element = $(this), method = element.is('button') ? 'html' : 'val';
+        element.data('ujs:enable-with', element[method]());
+        element[method](element.data('disable-with'));
+        element.prop('disabled', true);
       });
-      return xhr;
-    };
-  }
-  else $(document).ajaxSend(function(e, xhr) {
-    CSRFProtection(function(setHeader) { setHeader(xhr) });
-  });
+    },
+
+    /* Re-enables disabled form elements:
+      - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
+      - Sets disabled property to false
+    */
+    enableFormElements: function(form) {
+      form.find(rails.enableSelector).each(function() {
+        var element = $(this), method = element.is('button') ? 'html' : 'val';
+        if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
+        element.prop('disabled', false);
+      });
+    },
+
+   /* For 'data-confirm' attribute:
+      - Fires `confirm` event
+      - Shows the confirmation dialog
+      - Fires the `confirm:complete` event
 
-  // Triggers an event on an element and returns the event result
-  function fire(obj, name, data) {
-    var event = new $.Event(name);
-    obj.trigger(event, data);
-    return event.result !== false;
-  }
-
-  // Submits "remote" forms and links with ajax
-  function handleRemote(element) {
-    var method, url, data,
-      dataType = element.attr('data-type') || ($.ajaxSettings && $.ajaxSettings.dataType);
-
-    if (element.is('form')) {
-      method = element.attr('method');
-      url = element.attr('action');
-      data = element.serializeArray();
-      // memoized value from clicked submit button
-      var button = element.data('ujs:submit-button');
-      if (button) {
-        data.push(button);
-        element.data('ujs:submit-button', null);
+      Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
+      Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
+      Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
+      return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
+   */
+    allowAction: function(element) {
+      var message = element.data('confirm'),
+          answer = false, callback;
+      if (!message) { return true; }
+
+      if (rails.fire(element, 'confirm')) {
+        answer = rails.confirm(message);
+        callback = rails.fire(element, 'confirm:complete', [answer]);
       }
-    } else {
-      method = element.attr('data-method');
-      url = element.attr('href');
-      data = null;
-    }
+      return answer && callback;
+    },
 
-    $.ajax({
-      url: url, type: method || 'GET', data: data, dataType: dataType,
-      // stopping the "ajax:beforeSend" event will cancel the ajax request
-      beforeSend: function(xhr, settings) {
-        if (settings.dataType === undefined) {
-          xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
+    // Helper function which checks for blank inputs in a form that match the specified CSS selector
+    blankInputs: function(form, specifiedSelector, nonBlank) {
+      var inputs = $(), input,
+        selector = specifiedSelector || 'input,textarea';
+      form.find(selector).each(function() {
+        input = $(this);
+        // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
+        if (nonBlank ? input.val() : !input.val()) {
+          inputs = inputs.add(input);
         }
-        return fire(element, 'ajax:beforeSend', [xhr, settings]);
-      },
-      success: function(data, status, xhr) {
-        element.trigger('ajax:success', [data, status, xhr]);
-      },
-      complete: function(xhr, status) {
-        element.trigger('ajax:complete', [xhr, status]);
-      },
-      error: function(xhr, status, error) {
-        element.trigger('ajax:error', [xhr, status, error]);
+      });
+      return inputs.length ? inputs : false;
+    },
+
+    // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
+    nonBlankInputs: function(form, specifiedSelector) {
+      return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
+    },
+
+    // Helper function, needed to provide consistent behavior in IE
+    stopEverything: function(e) {
+      $(e.target).trigger('ujs:everythingStopped');
+      e.stopImmediatePropagation();
+      return false;
+    },
+
+    // find all the submit events directly bound to the form and
+    // manually invoke them. If anyone returns false then stop the loop
+    callFormSubmitBindings: function(form, event) {
+      var events = form.data('events'), continuePropagation = true;
+      if (events !== undefined && events['submit'] !== undefined) {
+        $.each(events['submit'], function(i, obj){
+          if (typeof obj.handler === 'function') return continuePropagation = obj.handler(event);
+        });
       }
-    });
-  }
-
-  // Handles "data-method" on links such as:
-  // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
-  function handleMethod(link) {
-    var href = link.attr('href'),
-      method = link.attr('data-method'),
-      csrf_token = $('meta[name=csrf-token]').attr('content'),
-      csrf_param = $('meta[name=csrf-param]').attr('content'),
-      form = $('<form method="post" action="' + href + '"></form>'),
-      metadata_input = '<input name="_method" value="' + method + '" type="hidden" />',
-      form_params = link.data('form-params');
-
-    if (csrf_param !== undefined && csrf_token !== undefined) {
-      metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
-    }
+      return continuePropagation;
+    },
+
+    //  replace element's html with the 'data-disable-with' after storing original html
+    //  and prevent clicking on it
+    disableElement: function(element) {
+      element.data('ujs:enable-with', element.html()); // store enabled state
+      element.html(element.data('disable-with')); // set to disabled state
+      element.bind('click.railsDisable', function(e) { // prevent further clicking
+        return rails.stopEverything(e)
+      });
+    },
 
-    // support non-nested JSON encoded params for links
-    if (form_params != undefined) {
-      var params = $.parseJSON(form_params);
-      for (key in params) {
-        form.append($("<input>").attr({"type": "hidden", "name": key, "value": params[key]}));
+    // restore element to its original state which was disabled by 'disableElement' above
+    enableElement: function(element) {
+      if (element.data('ujs:enable-with') !== undefined) {
+        element.html(element.data('ujs:enable-with')); // set to old enabled state
+        // this should be element.removeData('ujs:enable-with')
+        // but, there is currently a bug in jquery which makes hyphenated data attributes not get removed
+        element.data('ujs:enable-with', false); // clean up cache
       }
+      element.unbind('click.railsDisable'); // enable element
     }
 
-    form.hide().append(metadata_input).appendTo('body');
-    form.submit();
-  }
-
-  function disableFormElements(form) {
-    form.find('input[data-disable-with]').each(function() {
-      var input = $(this);
-      input.data('ujs:enable-with', input.val())
-        .val(input.attr('data-disable-with'))
-        .attr('disabled', 'disabled');
-    });
-  }
-
-  function enableFormElements(form) {
-    form.find('input[data-disable-with]').each(function() {
-      var input = $(this);
-      input.val(input.data('ujs:enable-with')).removeAttr('disabled');
-    });
-  }
-
-  function allowAction(element) {
-    var message = element.attr('data-confirm');
-    return !message || (fire(element, 'confirm') && confirm(message));
-  }
-
-  function requiredValuesMissing(form) {
-    var missing = false;
-    form.find('input[name][required]').each(function() {
-      if (!$(this).val()) missing = true;
-    });
-    return missing;
-  }
-
-  $('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) {
-    var link = $(this);
-    if (!allowAction(link)) return false;
+  };
+
+  $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
+
+  $(document).delegate(rails.linkDisableSelector, 'ajax:complete', function() {
+      rails.enableElement($(this));
+  });
 
-    if (link.attr('data-remote') != undefined) {
-      handleRemote(link);
+  $(document).delegate(rails.linkClickSelector, 'click.rails', function(e) {
+    var link = $(this), method = link.data('method'), data = link.data('params');
+    if (!rails.allowAction(link)) return rails.stopEverything(e);
+
+    if (link.is(rails.linkDisableSelector)) rails.disableElement(link);
+
+    if (link.data('remote') !== undefined) {
+      if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; }
+
+      if (rails.handleRemote(link) === false) { rails.enableElement(link); }
       return false;
-    } else if (link.attr('data-method')) {
-      handleMethod(link);
+
+    } else if (link.data('method')) {
+      rails.handleMethod(link);
       return false;
     }
   });
 
-  $('form').live('submit.rails', function(e) {
-    var form = $(this), remote = form.attr('data-remote') != undefined;
-    if (!allowAction(form)) return false;
+  $(document).delegate(rails.inputChangeSelector, 'change.rails', function(e) {
+    var link = $(this);
+    if (!rails.allowAction(link)) return rails.stopEverything(e);
+
+    rails.handleRemote(link);
+    return false;
+  });
+
+  $(document).delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
+    var form = $(this),
+      remote = form.data('remote') !== undefined,
+      blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
+      nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
 
-    // skip other logic when required values are missing
-    if (requiredValuesMissing(form)) return !remote;
+    if (!rails.allowAction(form)) return rails.stopEverything(e);
+
+    // skip other logic when required values are missing or file upload is present
+    if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
+      return rails.stopEverything(e);
+    }
 
     if (remote) {
-      handleRemote(form);
+      if (nonBlankFileInputs) {
+        return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
+      }
+
+      // If browser does not support submit bubbling, then this live-binding will be called before direct
+      // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
+      if (!$.support.submitBubbles && $().jquery < '1.7' && rails.callFormSubmitBindings(form, e) === false) return rails.stopEverything(e);
+
+      rails.handleRemote(form);
       return false;
+
     } else {
       // slight timeout so that the submit button gets properly serialized
-      setTimeout(function(){ disableFormElements(form) }, 13);
+      setTimeout(function(){ rails.disableFormElements(form); }, 13);
     }
   });
 
-  $('form input[type=submit], form button[type=submit], form button:not([type])').live('click.rails', function() {
+  $(document).delegate(rails.formInputClickSelector, 'click.rails', function(event) {
     var button = $(this);
-    if (!allowAction(button)) return false;
+
+    if (!rails.allowAction(button)) return rails.stopEverything(event);
+
     // register the pressed submit button
-    var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null;
+    var name = button.attr('name'),
+      data = name ? {name:name, value:button.val()} : null;
+
     button.closest('form').data('ujs:submit-button', data);
   });
 
-  $('form').live('ajax:beforeSend.rails', function(event) {
-    if (this == event.target) disableFormElements($(this));
+  $(document).delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) {
+    if (this == event.target) rails.disableFormElements($(this));
   });
 
-  $('form').live('ajax:complete.rails', function(event) {
-    if (this == event.target) enableFormElements($(this));
+  $(document).delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) {
+    if (this == event.target) rails.enableFormElements($(this));
   });
-})( jQuery );
 
+})( jQuery );
diff --git a/public/javascripts/vendor/jquery.iframe-transport.js b/public/javascripts/vendor/jquery.iframe-transport.js
new file mode 100644
index 0000000000000000000000000000000000000000..3407da0a539ea94b1995c889e094ee79cc0d9528
--- /dev/null
+++ b/public/javascripts/vendor/jquery.iframe-transport.js
@@ -0,0 +1,233 @@
+// This [jQuery](http://jquery.com/) plugin implements an `<iframe>`
+// [transport](http://api.jquery.com/extending-ajax/#Transports) so that
+// `$.ajax()` calls support the uploading of files using standard HTML file
+// input fields. This is done by switching the exchange from `XMLHttpRequest` to
+// a hidden `iframe` element containing a form that is submitted.
+
+// The [source for the plugin](http://github.com/cmlenz/jquery-iframe-transport)
+// is available on [Github](http://github.com/) and dual licensed under the MIT
+// or GPL Version 2 licenses.
+
+// ## Usage
+
+// To use this plugin, you simply add a `iframe` option with the value `true`
+// to the Ajax settings an `$.ajax()` call, and specify the file fields to
+// include in the submssion using the `files` option, which can be a selector,
+// jQuery object, or a list of DOM elements containing one or more
+// `<input type="file">` elements:
+
+//     $("#myform").submit(function() {
+//         $.ajax(this.action, {
+//             files: $(":file", this),
+//             iframe: true
+//         }).complete(function(data) {
+//             console.log(data);
+//         });
+//     });
+
+// The plugin will construct a hidden `<iframe>` element containing a copy of
+// the form the file field belongs to, will disable any form fields not
+// explicitly included, submit that form, and process the response.
+
+// If you want to include other form fields in the form submission, include them
+// in the `data` option, and set the `processData` option to `false`:
+
+//     $("#myform").submit(function() {
+//         $.ajax(this.action, {
+//             data: $(":text", this).serializeArray(),
+//             files: $(":file", this),
+//             iframe: true,
+//             processData: false
+//         }).complete(function(data) {
+//             console.log(data);
+//         });
+//     });
+
+// ### The Server Side
+
+// If the response is not HTML or XML, you (unfortunately) need to apply some
+// trickery on the server side. To send back a JSON payload, send back an HTML
+// `<textarea>` element with a `data-type` attribute that contains the MIME
+// type, and put the actual payload in the textarea:
+
+//     <textarea data-type="application/json">
+//       {"ok": true, "message": "Thanks so much"}
+//     </textarea>
+
+// The iframe transport plugin will detect this and attempt to apply the same
+// conversions that jQuery applies to regular responses. That means for the
+// example above you should get a Javascript object as the `data` parameter of
+// the `complete` callback, with the properties `ok: true` and
+// `message: "Thanks so much"`.
+
+// ### Compatibility
+
+// This plugin has primarily been tested on Safari 5, Firefox 4, and Internet
+// Explorer all the way back to version 6. While I haven't found any issues with
+// it so far, I'm fairly sure it still doesn't work around all the quirks in all
+// different browsers. But the code is still pretty simple overall, so you
+// should be able to fix it and contribute a patch :)
+
+// ## Annotated Source
+
+(function($, undefined) {
+
+  // Register a prefilter that checks whether the `iframe` option is set, and
+  // switches to the iframe transport if it is `true`.
+  $.ajaxPrefilter(function(options, origOptions, jqXHR) {
+    if (options.iframe) {
+      return "iframe";
+    }
+  });
+
+  // Register an iframe transport, independent of requested data type. It will
+  // only activate when the "files" option has been set to a non-empty list of
+  // enabled file inputs.
+  $.ajaxTransport("iframe", function(options, origOptions, jqXHR) {
+    var form = null,
+        iframe = null,
+        origAction = null,
+        origTarget = null,
+        origEnctype = null,
+        addedFields = [],
+        disabledFields = [],
+        files = $(options.files).filter(":file:enabled");
+
+    // This function gets called after a successful submission or an abortion
+    // and should revert all changes made to the page to enable the
+    // submission via this transport.
+    function cleanUp() {
+      $(addedFields).each(function() {
+        this.remove();
+      });
+      $(disabledFields).each(function() {
+        this.disabled = false;
+      });
+      form.attr("action", origAction || "")
+          .attr("target", origTarget || "")
+          .attr("enctype", origEnctype || "");
+      iframe.attr("src", "javascript:false;").remove();
+    }
+
+    // Remove "iframe" from the data types list so that further processing is
+    // based on the content type returned by the server, without attempting an
+    // (unsupported) conversion from "iframe" to the actual type.
+    options.dataTypes.shift();
+
+    if (files.length) {
+      // Determine the form the file fields belong to, and make sure they all
+      // actually belong to the same form.
+      files.each(function() {
+        if (form !== null && this.form !== form) {
+          jQuery.error("All file fields must belong to the same form");
+        }
+        form = this.form;
+      });
+      form = $(form);
+
+      // Store the original form attributes that we'll be replacing temporarily.
+      origAction = form.attr("action");
+      origTarget = form.attr("target");
+      origEnctype = form.attr("enctype");
+
+      // We need to disable all other inputs in the form so that they don't get
+      // included in the submitted data unexpectedly.
+      form.find(":input:not(:submit)").each(function() {
+        if (!this.disabled && (this.type != "file" || files.index(this) < 0)) {
+          this.disabled = true;
+          disabledFields.push(this);
+        }
+      });
+
+      // If there is any additional data specified via the `data` option,
+      // we add it as hidden fields to the form. This (currently) requires
+      // the `processData` option to be set to false so that the data doesn't
+      // get serialized to a string.
+      if (typeof(options.data) === "string" && options.data.length > 0) {
+        jQuery.error("data must not be serialized");
+      }
+      $.each(options.data || {}, function(name, value) {
+        if ($.isPlainObject(value)) {
+          name = value.name;
+          value = value.value;
+        }
+        addedFields.push($("<input type='hidden'>").attr("name", name)
+          .attr("value", value).appendTo(form));
+      });
+
+      // Add a hidden `X-Requested-With` field with the value `IFrame` to the
+      // field, to help server-side code to determine that the upload happened
+      // through this transport.
+      addedFields.push($("<input type='hidden' name='X-Requested-With'>")
+        .attr("value", "IFrame").appendTo(form));
+
+      // Borrowed straight from the JQuery source
+      // Provides a way of specifying the accepted data type similar to HTTP_ACCEPTS
+      accepts = options.dataTypes[ 0 ] && options.accepts[ options.dataTypes[0] ] ?
+        options.accepts[ options.dataTypes[0] ] + ( options.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
+        options.accepts[ "*" ]
+
+      addedFields.push($("<input type='hidden' name='X-Http-Accept'>")
+        .attr("value", accepts).appendTo(form));
+
+      return {
+
+        // The `send` function is called by jQuery when the request should be
+        // sent.
+        send: function(headers, completeCallback) {
+          iframe = $("<iframe src='javascript:false;' name='iframe-" + $.now()
+            + "' style='display:none'></iframe>");
+
+          // The first load event gets fired after the iframe has been injected
+          // into the DOM, and is used to prepare the actual submission.
+          iframe.bind("load", function() {
+
+            // The second load event gets fired when the response to the form
+            // submission is received. The implementation detects whether the
+            // actual payload is embedded in a `<textarea>` element, and
+            // prepares the required conversions to be made in that case.
+            iframe.unbind("load").bind("load", function() {
+
+              var doc = this.contentWindow ? this.contentWindow.document :
+                (this.contentDocument ? this.contentDocument : this.document),
+                root = doc.documentElement ? doc.documentElement : doc.body,
+                textarea = root.getElementsByTagName("textarea")[0],
+                type = textarea ? textarea.getAttribute("data-type") : null;
+
+              var status = textarea ? parseInt(textarea.getAttribute("response-code")) : 200,
+                statusText = "OK",
+                responses = { text: type ? textarea.value : root ? root.innerHTML : null },
+                headers = "Content-Type: " + (type || "text/html")
+
+              completeCallback(status, statusText, responses, headers);
+
+              setTimeout(cleanUp, 50);
+            });
+
+            // Now that the load handler has been set up, reconfigure and
+            // submit the form.
+            form.attr("action", options.url)
+              .attr("target", iframe.attr("name"))
+              .attr("enctype", "multipart/form-data")
+              .get(0).submit();
+          });
+
+          // After everything has been set up correctly, the iframe gets
+          // injected into the DOM so that the submission can be initiated.
+          iframe.insertAfter(form);
+        },
+
+        // The `abort` function is called by jQuery when the request should be
+        // aborted.
+        abort: function() {
+          if (iframe !== null) {
+            iframe.unbind("load").attr("src", "javascript:false;");
+            cleanUp();
+          }
+        }
+
+      };
+    }
+  });
+
+})(jQuery);
diff --git a/public/javascripts/vendor/jquery.remotipart.js b/public/javascripts/vendor/jquery.remotipart.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e00854e00937bf108c2ad7e730e83bf90e70da5
--- /dev/null
+++ b/public/javascripts/vendor/jquery.remotipart.js
@@ -0,0 +1,69 @@
+//= require jquery.iframe-transport.js
+//= require_self
+
+(function($) {
+
+  var remotipart;
+
+  $.remotipart = remotipart = {
+
+    setup: function(form) {
+      form
+        // Allow setup part of $.rails.handleRemote to setup remote settings before canceling default remote handler
+        // This is required in order to change the remote settings using the form details
+        .one('ajax:beforeSend.remotipart', function(e, xhr, settings){
+          // Delete the beforeSend bindings, since we're about to re-submit via ajaxSubmit with the beforeSubmit
+          // hook that was just setup and triggered via the default `$.rails.handleRemote`
+          // delete settings.beforeSend;
+          delete settings.beforeSend;
+
+          settings.iframe      = true;
+          settings.files       = $($.rails.fileInputSelector, form);
+          settings.data        = form.serializeArray();
+          settings.processData = false;
+
+          // Modify some settings to integrate JS request with rails helpers and middleware
+          if (settings.dataType === undefined) { settings.dataType = 'script *'; }
+          settings.data.push({name: 'remotipart_submitted', value: true});
+
+          // Allow remotipartSubmit to be cancelled if needed
+          if ($.rails.fire(form, 'ajax:remotipartSubmit', [xhr, settings])) {
+            // Second verse, same as the first
+            $.rails.ajax(settings);
+          }
+
+          //Run cleanup
+          remotipart.teardown(form);
+
+          // Cancel the jQuery UJS request
+          return false;
+        })
+
+        // Keep track that we just set this particular form with Remotipart bindings
+        // Note: The `true` value will get over-written with the `settings.dataType` from the `ajax:beforeSend` handler
+        .data('remotipartSubmitted', true);
+    },
+
+    teardown: function(form) {
+      form
+        .unbind('ajax:beforeSend.remotipart')
+        .removeData('remotipartSubmitted')
+    }
+  };
+
+  $('form').live('ajax:aborted:file', function(){
+    var form = $(this);
+
+    remotipart.setup(form);
+
+    // If browser does not support submit bubbling, then this live-binding will be called before direct
+    // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
+    if (!$.support.submitBubbles && $().jquery < '1.7' && $.rails.callFormSubmitBindings(form) === false) return $.rails.stopEverything(e);
+
+    // Manually call jquery-ujs remote call so that it can setup form and settings as usual,
+    // and trigger the `ajax:beforeSend` callback to which remotipart binds functionality.
+    $.rails.handleRemote(form);
+    return false;
+  });
+
+})(jQuery);