Commit 4937ef0f

mo khan <mo@mokhan.ca>
2014-06-18 05:01:01
upload files using backbone and file api.
1 parent 5b70fec
Changed files (6)
app
assets
javascripts
controllers
vendor
app/assets/javascripts/backbone/models/photo.js.coffee
@@ -1,5 +1,6 @@
 class Cake.Models.Photo extends Backbone.Model
   paramRoot: 'photo'
+  fileAttribute: 'image'
 
   defaults:
     thumb_url: null
@@ -8,7 +9,6 @@ class Cake.Models.Photo extends Backbone.Model
 
 class Cake.Collections.PhotosCollection extends Backbone.Collection
   model: Cake.Models.Photo
-  url: '/api/v1/cakes/1/photos'
 
   initialize: (options) ->
     @url="/api/v1/cakes/#{options.cake_id}/photos"
app/assets/javascripts/backbone/templates/photos/new.jst.ejs
@@ -3,9 +3,9 @@
 <form id="new-photo" name="photo" class="form-horizontal">
   <fieldset>
     <div class="control-group">
-      <label class="control-label" for="photo_image">Photo</label>
+      <label class="control-label" for="photo_attachment">Photo</label>
       <div class="controls">
-        <input class="input-xxlarge" id="photo_image" name="image" type="file">
+        <input class="input-xxlarge" id="photo_attachment" name="attachment" type="file">
       </div>
     </div>
     <div class="form-actions">
app/assets/javascripts/backbone/views/photos/new_view.js.coffee
@@ -20,14 +20,24 @@ class Cake.Views.Photos.NewView extends Backbone.View
 
     @model.unset("errors")
 
-    @collection.create(@model.toJSON(),
-      success: (photo) =>
-        @model = photo
-        window.location.hash = "/#{@model.id}"
-
-      error: (photo, jqXHR) =>
-        @model.set({errors: $.parseJSON(jqXHR.responseText)})
-    )
+    fileObject = @$(':input[type="file"]')[0].files[0]
+    photo = new Cake.Models.Photo(cake_id: 100)
+    photo.url = => @collection.url
+    photo.set('image', fileObject)
+    photo.on('progress', console.log)
+    photo.save()
+
+    #@model.set('image', fileObject)
+    #@model.save()
+    #@model.on('progress', console.log)
+    #@collection.create(@model.toJSON(),
+      #success: (photo) =>
+        #@model = photo
+        #window.location.hash = "/#{@model.id}"
+
+      #error: (photo, jqXHR) =>
+        #@model.set({errors: $.parseJSON(jqXHR.responseText)})
+    #)
 
   render: ->
     $(@el).html(@template(@model.toJSON() ))
app/assets/javascripts/application.js
@@ -28,4 +28,5 @@
 //= require backbone_rails_sync
 //= require backbone_datalink
 //= require backbone/cake
+//= require backbone-model-file-upload
 //= require_tree .
app/controllers/api/v1/photos_controller.rb
@@ -5,7 +5,7 @@ module Api
 
       def create
         cake_id = params[:cake_id]
-        UploadPhoto.new.run(cake_id, photo_params)
+        UploadPhoto.new.run(cake_id, params)
         @photo = Creation.find(cake_id).photos.last
         respond_with(@photo)
       end
vendor/assets/javascripts/backbone-model-file-upload.js
@@ -0,0 +1,117 @@
+//     Backbone.Model File Upload v0.1
+//     by Joe Vu - joe.vu@homeslicesolutions.com
+//     For all details and documentation:
+//     https://github.com/homeslicesolutions/backbone-model-file-upload
+
+!function(_, Backbone){
+
+  // Clone the original Backbone.Model.prototype
+  var backboneModelClone = _.clone( Backbone.Model.prototype );
+
+  // Extending out
+  _.extend(Backbone.Model.prototype, {  
+
+    // ! Default file attribute - can be overwritten
+    fileAttribute: 'file',
+
+    // @ Save - overwritten
+    save: function(key, val, options) {
+
+      // Variables
+      var attrs, attributes = this.attributes;
+
+      // Signature parsing - taken directly from original Backbone.Model.save 
+      // and it states: 'Handle both "key", value and {key: value} -style arguments.'
+      if (key == null || typeof key === 'object') {
+        attrs = key;
+        options = val;
+      } else {
+        (attrs = {})[key] = val;
+      }
+
+      // Validate & wait options - taken directly from original Backbone.Model.save
+      options = _.extend({validate: true}, options);
+      if (attrs && !options.wait) {
+        if (!this.set(attrs, options)) return false;
+      } else {
+        if (!this._validate(attrs, options)) return false;
+      }
+      if (attrs && options.wait) {
+        this.attributes = _.extend({}, attributes, attrs);
+      }
+
+      // Check for "formData" flag and check for if file exist.
+      if ( options.formData === true 
+           || options.formData !== false 
+              && this.attributes[ this.fileAttribute ] 
+              && this.attributes[ this.fileAttribute ] instanceof File ) {
+        
+        // Flatten Attributes reapplying File Object
+        var formAttrs = _.clone( this.attributes ),
+            fileAttr = this.attributes[ this.fileAttribute ];
+        formAttrs = this._flatten( formAttrs );
+        formAttrs[ this.fileAttribute ] = fileAttr;
+
+        // Converting Attributes to Form Data
+        var formData = new FormData();
+        _.each( formAttrs, function( value, key ){
+          formData.append( key, value );
+        });
+
+        // Set options for AJAX call
+        options = options || {};
+        options.data = formData;
+        options.processData = false;
+        options.contentType = false;
+
+        // Apply custom XHR for processing status & listen to "progress"
+        var that = this;
+        options.xhr = function() {
+          var xhr = $.ajaxSettings.xhr();
+          xhr.upload.addEventListener('progress', function(){
+
+            that._progressHandler.apply(that, arguments);
+          }, false);
+          return xhr;
+        }    
+      }
+
+      // Resume back to original state
+      if (attrs && options.wait) this.attributes = attributes;
+
+      // Continue to call the existing "save" method
+      return backboneModelClone.save.call(this, attrs, options);
+      
+    },
+
+    // _ FlattenObject gist by "penguinboy".  Thank You!
+    // https://gist.github.com/penguinboy/762197
+    _flatten: function( obj ) {
+      var output = {};
+      for (var i in obj) {
+        if (!obj.hasOwnProperty(i)) continue;
+        if (typeof obj[i] == 'object') {
+          var flatObject = this._flatten(obj[i]);
+          for (var x in flatObject) {
+            if (!flatObject.hasOwnProperty(x)) continue;
+            output[i + '.' + x] = flatObject[x];
+          }
+        } else {
+          output[i] = obj[i];
+        }
+      }
+      return output;
+
+    },
+    
+    // _ Get the Progress of the uploading file
+    _progressHandler: function( event ) {
+      if (event.lengthComputable) {
+        var percentComplete = event.loaded / event.total;
+        this.trigger( 'progress', percentComplete );
+      }
+    }
+
+  });
+
+}(_, Backbone);