master
  1//     Backbone.Model File Upload v0.1
  2//     by Joe Vu - joe.vu@homeslicesolutions.com
  3//     For all details and documentation:
  4//     https://github.com/homeslicesolutions/backbone-model-file-upload
  5
  6!function(_, Backbone){
  7  // Clone the original Backbone.Model.prototype
  8  var backboneModelClone = _.clone( Backbone.Model.prototype );
  9
 10  // Extending out
 11  _.extend(Backbone.Model.prototype, {
 12
 13    // ! Default file attribute - can be overwritten
 14    fileAttribute: 'file',
 15
 16    // @ Save - overwritten
 17    save: function(key, val, options) {
 18
 19      // Variables
 20      var attrs, attributes = this.attributes;
 21
 22      // Signature parsing - taken directly from original Backbone.Model.save 
 23      // and it states: 'Handle both "key", value and {key: value} -style arguments.'
 24      if (key == null || typeof key === 'object') {
 25        attrs = key;
 26        options = val;
 27      } else {
 28        (attrs = {})[key] = val;
 29      }
 30
 31      // Validate & wait options - taken directly from original Backbone.Model.save
 32      options = _.extend({validate: true}, options);
 33      if (attrs && !options.wait) {
 34        if (!this.set(attrs, options)) return false;
 35      } else {
 36        if (!this._validate(attrs, options)) return false;
 37      }
 38      if (attrs && options.wait) {
 39        this.attributes = _.extend({}, attributes, attrs);
 40      }
 41
 42      // Check for "formData" flag and check for if file exist.
 43      if ( options.formData === true 
 44           || options.formData !== false 
 45              && this.attributes[ this.fileAttribute ] 
 46              && this.attributes[ this.fileAttribute ] instanceof File ) {
 47        
 48        // Flatten Attributes reapplying File Object
 49        var formAttrs = _.clone( this.attributes ),
 50            fileAttr = this.attributes[ this.fileAttribute ];
 51        formAttrs = this._flatten( formAttrs );
 52        formAttrs[ this.fileAttribute ] = fileAttr;
 53
 54        // Converting Attributes to Form Data
 55        var formData = new FormData();
 56        _.each( formAttrs, function( value, key ){
 57          formData.append( key, value );
 58        });
 59
 60        // Set options for AJAX call
 61        options = options || {};
 62        options.data = formData;
 63        options.processData = false;
 64        options.contentType = false;
 65
 66        // Apply custom XHR for processing status & listen to "progress"
 67        var that = this;
 68        options.xhr = function() {
 69          var xhr = $.ajaxSettings.xhr();
 70          xhr.upload.addEventListener('progress', function(){
 71
 72            that._progressHandler.apply(that, arguments);
 73          }, false);
 74          return xhr;
 75        }    
 76      }
 77
 78      // Resume back to original state
 79      if (attrs && options.wait) this.attributes = attributes;
 80
 81      // Continue to call the existing "save" method
 82      return backboneModelClone.save.call(this, attrs, options);
 83    },
 84
 85    // _ FlattenObject gist by "penguinboy".  Thank You!
 86    // https://gist.github.com/penguinboy/762197
 87    _flatten: function( obj ) {
 88      var output = {};
 89      for (var i in obj) {
 90        if (!obj.hasOwnProperty(i)) continue;
 91        if (typeof obj[i] == 'object') {
 92          var flatObject = this._flatten(obj[i]);
 93          for (var x in flatObject) {
 94            if (!flatObject.hasOwnProperty(x)) continue;
 95            output[i + '.' + x] = flatObject[x];
 96          }
 97        } else {
 98          output[i] = obj[i];
 99        }
100      }
101      return output;
102
103    },
104
105    // _ Get the Progress of the uploading file
106    _progressHandler: function( event ) {
107      console.log(event);
108      if (event.lengthComputable) {
109        var percentComplete = event.loaded / event.total;
110        console.log("triggering... " + percentComplete);
111        this.trigger( 'progress', percentComplete );
112      }
113    }
114  });
115}(_, Backbone);