Merge pull request #13 from homeslicesolutions/Refactoring-the-class

Major refactor
This commit is contained in:
Joe Vu 2014-12-17 04:00:39 -08:00
commit 243d8bb619
8 changed files with 183 additions and 185 deletions

View File

@ -4,7 +4,7 @@ module.exports = function(grunt) {
run_node: {
start: {
files: { src: [ 'tests/mock-file-server.js'] }
files: { src: [ 'test/mock-file-server.js'] }
}
},
@ -35,41 +35,50 @@ module.exports = function(grunt) {
},
jasmine: {
host: 'http://localhost:8888/',
options: {
specs: ['tests/*spec.js'],
//keepRunner: true,
vendor: [
"bower_components/Blob/Blob.js",
"bower_components/jquery/dist/jquery.min.js",
"bower_components/underscore/underscore-min.js",
"bower_components/backbone/backbone.js",
"backbone-model-file-upload.js"
]
}
},
amd: {
src: 'backbone-model-file-upload.js',
host: 'http://localhost:8888/',
options: {
specs: ['test/*spec.js'],
helpers: 'bower_components/Blob/Blob.js',
//keepRunner: true,
template: require('grunt-template-jasmine-requirejs'),
templateOptions: {
requireConfig: {
paths: {
"jquery": "bower_components/jquery/dist/jquery.min",
"underscore": "bower_components/underscore/underscore-min",
"backbone": "bower_components/backbone/backbone"
}
}
},
parallel: {
runTest: {
tasks: [{
grunt: true,
args: ['run:mockServer']
}, {
grunt: true,
args: ['jasmine']
}, {
cmd: 'node:kill'
}]
}
},
browserGlobal: {
src: 'backbone-model-file-upload.js',
host: 'http://localhost:8888/',
options: {
specs: ['test/*spec.js'],
//keepRunner: true,
vendor: [
"bower_components/Blob/Blob.js",
"bower_components/jquery/dist/jquery.min.js",
"bower_components/underscore/underscore-min.js",
"bower_components/backbone/backbone.js"
]
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-run');
grunt.loadNpmTasks('grunt-parallel');
grunt.loadNpmTasks('grunt-run-node');
grunt.registerTask('test', ['run:installBower','run_node','jasmine','stop_node']);
grunt.registerTask('test', ['build','run_node','jasmine','stop_node']);
grunt.registerTask('build', ['run:installBower']);
grunt.registerTask('resetNodeWin', ['run:killAllNodeWindows']);
grunt.registerTask('resetNodeMac', ['run:killAllNodeMac']);

View File

@ -1,8 +1,9 @@
Backbone.Model File Upload
==========================
![alt tag](https://travis-ci.org/homeslicesolutions/backbone-model-file-upload.svg?branch=master)
A concise, non-iframe, & pure XHR2/AJAX Backbone.model file upload. (Good for IE >= 10, FF, Chrome.)
![alt tag](https://travis-ci.org/homeslicesolutions/backbone-model-file-upload.svg?branch=master)
This plugin upgrades the current `save` method to be able to upload files using the HTML5's File API and FormData class.
@ -162,18 +163,20 @@ c Version v0.1
#### Version 0.5.2
- Add jQuery to the UMD dependency model
-
#### Version 0.5.3
- Added CommonJS support
### Dev/Installation
If you want to work on this plugin, test it, etc., it just needs an install of `node` and `grunt`.
```
npm i -d
```
To build
```
grunt build
```
To test
```
npm test
npm test OR grunt test
```
### Future plans
As I'm looking at this plugin and all the plugins, I'm not really extending out the Backbone class like how it should. Also people have been asking me to write a mixin version instead. Also there should be a CommonJS version as well. If any of you guys want to help please do. That would be great!

View File

@ -1,4 +1,4 @@
// Backbone.Model File Upload v0.5.2
// Backbone.Model File Upload v0.5.3
// by Joe Vu - joe.vu@homeslicesolutions.com
// For all details and documentation:
// https://github.com/homeslicesolutions/backbone-model-file-upload
@ -7,21 +7,32 @@
// bildja - Dima Bildin - github.com/bildja
// Minjung - Alejandro - github.com/Minjung
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['underscore', 'jquery', 'backbone'], factory);
} else {
// Browser globals
factory(_, $, Backbone);
}
}(this, function(_, $, Backbone){
(function(root, factory) {
// Clone the original Backbone.Model.prototype
var backboneModelClone = _.clone( Backbone.Model.prototype );
// AMD
if (typeof define === 'function' && define.amd) {
define(['underscore', 'jquery', 'backbone'], function(_, $, Backbone){
factory(root, Backbone, _, $);
});
// NodeJS/CommonJS
} else if (typeof exports !== 'undefined') {
var _ = require('underscore'), $ = require('jquery'), Backbone = require('backbone');
factory(root, _, $, Backbone);
// Browser global
} else {
factory(root, root.Backbone, root._, root.$);
}
}(this, function(root, Backbone, _, $) {
'use strict';
// Clone the original Backbone.Model.prototype as superClass
var _superClass = _.clone( Backbone.Model.prototype );
// Extending out
_.extend(Backbone.Model.prototype, {
var BackboneModelFileUpload = Backbone.Model.extend({
// ! Default file attribute - can be overwritten
fileAttribute: 'file',
@ -58,15 +69,15 @@
// 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 ) {
|| 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 ];
fileAttr = mergedAttrs[ this.fileAttribute ];
formAttrs = this._flatten( formAttrs );
formAttrs[ this.fileAttribute ] = fileAttr;
@ -100,7 +111,7 @@
if (attrs && options.wait) this.attributes = attributes;
// Continue to call the existing "save" method
return backboneModelClone.save.call(this, attrs, options);
return _superClass.save.call(this, attrs, options);
},
@ -131,7 +142,9 @@
this.trigger( 'progress', percentComplete );
}
}
});
// Export out to override Backbone Model
Backbone.Model = BackboneModelFileUpload;
}));

View File

@ -3,6 +3,7 @@
"dependencies": {
"jquery": "~2.1.1",
"backbone": "~1.1.2",
"Blob": "*"
"Blob": "*",
"requirejs": "~2.1.15"
}
}

View File

@ -13,6 +13,7 @@
"grunt-contrib-jasmine": "^0.6.5",
"grunt-run": "^0.3.0",
"grunt-run-node": "^0.1.0",
"grunt-template-jasmine-requirejs": "^0.2.0",
"lodash": "~2.4.1"
},
"scripts": {

View File

@ -14,6 +14,8 @@
fileAttribute: 'fileAttachment'
});
var fileModel;
var simulatedFileObj;
@ -136,7 +138,7 @@
// Act
fileModel.set({from: 'somethingelse@email.com'});
fileModel.save(null);
fileModel.save();
});

91
test/mock-file-server.js Normal file
View File

@ -0,0 +1,91 @@
'use strict';
var http = require('http'),
formidable = require('formidable'),
fs = require('fs'),
_ = require('lodash');
var options = {
host: 'localhost',
port: 8989
};
var server = http.createServer(function(req,res) {
console.log('POST Detected with incoming Form-data...');
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
console.log('POST Finished and publishing object back to browser...');
var headers = {};
headers['Content-Type'] = 'application/json';
headers["Access-Control-Allow-Origin"] = req.headers.origin;
headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
headers["Access-Control-Allow-Credentials"] = true;
headers["Access-Control-Max-Age"] = '86400'; // 24 hours
headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Access-Control-Allow-Origin, Content-Type, Accept";
res.writeHead(200, headers);
var output = {};
_.extend(output, unflatten(fields));
if (_.isEmpty(files)) return res.end(JSON.stringify(output));
for (var i in files) {
if (files.hasOwnProperty(i)) {
output[i] = {};
fs.readFile(files[i].path, function (err, data) {
output[i].type = files[i].type;
output[i].size = files[i].size;
output[i].name = files[i].name;
output[i].lastModifiedDate = files[i].lastModifiedDate;
output[i].data = 'data:' + files[i].type + ';base64,' + data.toString('base64');
res.end(JSON.stringify(output));
});
}
}
});
});
var unflatten = function(path, obj, value) {
var key, child, output;
if (Object.prototype.toString.call(path) == '[object Object]') {
output = {};
for (key in path) {
if (path.hasOwnProperty(key)) {
unflatten(key.split('.'), output, path[key]);
}
}
return output;
}
key = path.shift();
if (!path.length) {
obj[key] = value;
return obj;
}
if ((child = obj[key]) == void 0) {
child = obj[key] = {};
}
unflatten(path, child, value);
return obj;
};
server.listen(options.port, options.host);
console.log("listening on " + options.host + ':' + options.port);

View File

@ -1,122 +0,0 @@
'use strict';
var http = require('http'),
formidable = require('formidable'),
fs = require('fs'),
_ = require('lodash');
var options = {
host: 'localhost',
port: 8989
};
var unflatten = function(path, obj, value) {
var key, child, output;
if (Object.prototype.toString.call(path) == '[object Object]') {
output = {};
for (key in path) {
if (path.hasOwnProperty(key)) {
unflatten(key.split('.'), output, path[key]);
}
}
return output;
}
key = path.shift();
if (!path.length) {
obj[key] = value;
return obj;
}
if ((child = obj[key]) == void 0) {
child = obj[key] = {};
}
unflatten(path, child, value);
return obj;
};
var server = http.createServer(function(req,res) {
// Capture POST call
//if (req.method == 'POST' && req.headers['content-type'].match('application/json')) {
//
// console.log('POST Detected with incoming JSON...');
//
// var body = '';
// req.on('data', function (data) {
// body += data;
// console.log('POST Data: ' + data);
// });
//
// req.on('end', function () {
// console.log('POST Finished and publishing object back to browser...');
// res.writeHead(200, {'Content-Type': 'application/json'});
// res.end(body);
// });
//
//
//
//} else if ((req.method == 'POST' || req.method == 'OPTIONS') && req.headers['content-type'].match('multipart/form-data') > 0) {
console.log('POST Detected with incoming Form-data...');
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
console.log('POST Finished and publishing object back to browser...');
var headers = {};
headers['Content-Type'] = 'application/json';
headers["Access-Control-Allow-Origin"] = req.headers.origin;
headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
headers["Access-Control-Allow-Credentials"] = true;
headers["Access-Control-Max-Age"] = '86400'; // 24 hours
headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Access-Control-Allow-Origin, Content-Type, Accept";
res.writeHead(200, headers);
var output = {};
_.extend(output, unflatten(fields));
if (_.isEmpty(files)) return res.end(JSON.stringify(output));
for (var i in files) {
if (files.hasOwnProperty(i)) {
output[i] = {};
fs.readFile(files[i].path, function (err, data) {
output[i].type = files[i].type;
output[i].size = files[i].size;
output[i].name = files[i].name;
output[i].lastModifiedDate = files[i].lastModifiedDate;
output[i].data = 'data:' + files[i].type + ';base64,' + data.toString('base64');
res.end(JSON.stringify(output));
});
}
}
});
//} else {
// console.log('GET Detected...');
// console.log('GET Returning OK');
// res.writeHead(200, {'Content-Type': 'text/html'})
// res.end('OK');
//}
});
server.listen(options.port, options.host);
console.log("listening on " + options.host + ':' + options.port);