backbone-model-file-upload/backbone-model-file-upload.js

177 lines
5.6 KiB
JavaScript

// Backbone.Model File Upload v1.0.0
// by Joe Vu - joe.vu@homeslicesolutions.com
// For all details and documentation:
// https://github.com/homeslicesolutions/backbone-model-file-upload
// Contributors:
// lutherism - Alex Jansen - alex.openrobot.net
// bildja - Dima Bildin - github.com/bildja
// Minjung - Alejandro - github.com/Minjung
// XemsDoom - Luca Moser - https://github.com/XemsDoom
// DanilloCorvalan - Danillo Corvalan - https://github.com/DanilloCorvalan
(function(root, factory) {
// AMD
if (typeof define === 'function' && define.amd) {
define(['underscore', 'backbone'], function(_, Backbone){
factory(root, Backbone, _);
});
// NodeJS/CommonJS
} else if (typeof exports !== 'undefined') {
var _ = require('underscore'), Backbone = require('backbone');
factory(root, Backbone, _);
// Browser global
} else {
factory(root, root.Backbone, root._);
}
}(this, function(root, Backbone, _) {
'use strict';
// Clone the original Backbone.Model.prototype as superClass
var _superClass = _.clone( Backbone.Model.prototype );
// Extending out
var BackboneModelFileUpload = Backbone.Model.extend({
// ! Default file attribute - can be overwritten
fileAttribute: 'file',
// @ Save - overwritten
save: function(key, val, options) {
// Variables
var attrs, attributes = this.attributes,
that = this;
// 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;
}
// Merge data temporarily for formdata
var mergedAttrs = _.extend({}, attributes, attrs);
if (attrs && options.wait) {
this.attributes = mergedAttrs;
}
// Check for "formData" flag and check for if file exist.
if ( options.formData === true
|| options.formData !== false
&& mergedAttrs[ this.fileAttribute ]
&& mergedAttrs[ this.fileAttribute ] instanceof File
|| mergedAttrs[ this.fileAttribute ] instanceof FileList
|| mergedAttrs[ this.fileAttribute ] instanceof Blob ) {
// Flatten Attributes reapplying File Object
var formAttrs = _.clone( mergedAttrs ),
fileAttr = mergedAttrs[ this.fileAttribute ];
formAttrs = this._flatten( formAttrs );
formAttrs[ this.fileAttribute ] = fileAttr;
// Converting Attributes to Form Data
var formData = new FormData();
_.each( formAttrs, function( value, key ){
if (value instanceof FileList || (key === that.fileAttribute && value instanceof Array)) {
_.each(value, function(file) {
formData.append( key, file );
});
}
else {
formData.append( key, value );
}
});
// Set options for AJAX call
options.data = formData;
options.processData = false;
options.contentType = false;
// Handle "progress" events
if (!options.xhr) {
options.xhr = function(){
var xhr = Backbone.$.ajaxSettings.xhr();
xhr.upload.addEventListener('progress', _.bind(that._progressHandler, that), false);
return xhr
}
}
}
// Resume back to original state
if (attrs && options.wait) this.attributes = attributes;
// Continue to call the existing "save" method
return _superClass.save.call(this, attrs, options);
},
// _ FlattenObject gist by "penguinboy". Thank You!
// https://gist.github.com/penguinboy/762197
// NOTE for those who use "<1.0.0". The notation changed to nested brackets
_flatten: function flatten( obj ) {
var output = {};
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
var flatObject = 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;
},
// An "Unflatten" tool which is something normally should be on the backend
// But this is a guide to how you would unflatten the object
_unflatten: function unflatten(obj, output) {
var re = /^([^\[\]]+)\[(.+)\]$/g;
output = output || {};
for (var key in obj) {
var value = obj[key];
if (!key.toString().match(re)) {
var tempOut = {};
tempOut[key] = value;
_.extend(output, tempOut);
} else {
var keys = _.compact(key.split(re)), tempOut = {};
tempOut[keys[1]] = value;
output[keys[0]] = unflatten( tempOut, output[keys[0]] )
}
}
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 );
}
}
});
// Export out to override Backbone Model
Backbone.Model = BackboneModelFileUpload;
}));