Skip to content

Instantly share code, notes, and snippets.

@philfreo
Last active September 18, 2017 15:45
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save philfreo/3877309 to your computer and use it in GitHub Desktop.
Save philfreo/3877309 to your computer and use it in GitHub Desktop.
Backbone-Forms File Upload Editor
// directly uploads to S3
// See http://philfreo.com/blog/how-to-allow-direct-file-uploads-from-javascript-to-amazon-s3-signed-by-python/
// See https://github.com/elasticsales/s3upload-coffee-javascript
editors.Filepicker = editors.Text.extend({
tagName: 'div',
events: {
'change input[type=file]': 'uploadFile',
'click .remove': 'removeFile'
},
initialize: function(options) {
_.bindAll(this, 'filepickerSuccess', 'filepickerError', 'filepickerProgress');
editors.Text.prototype.initialize.call(this, options);
this.$input = $('<input type="hidden" name="'+this.key+'" />');
this.$uploadInput = $('<input type="file" multiple="multiple" />');
this.$loader = $('<p class="upload-status"><span class="loader"></span> Uploading&hellip;</p>');
this.$error = $('<p class="upload-error error">Error</p>');
this.$list = $('<ul class="file-list">');
},
// return an array of file dicts
getValue: function() {
var val = this.$input.val();
return val ? JSON.parse(val) : [];
},
setValue: function(value) {
var str, files = value;
if (_(value).isObject()) {
str = JSON.stringify(value);
} else {
files = value ? JSON.parse(value) : [];
}
this.$input.val(str);
this.updateList(files);
},
render: function(options) {
editors.Text.prototype.render.apply(this, arguments);
this.$el.append(this.$input);
this.$el.append(this.$uploadInput);
this.$el.append(this.$loader.hide());
this.$el.append(this.$error.hide());
this.$el.append(this.$list);
return this;
},
uploadFile: function() {
var s3upload = new S3Upload({
file_dom_selector: this.$uploadInput,
s3_sign_put_url: '/sign_s3_put/',
onProgress: this.filepickerProgress,
onFinishS3Put: this.filepickerSuccess,
onError: this.filepickerError
});
},
filepickerSuccess: function(s3Url, file) {
console.log('File uploaded', s3Url);
this.$loader.hide();
this.$error.hide();
this.$uploadInput.val('');
var newFiles = [{
url: s3Url,
filename: file.name,
size: file.size,
content_type: file.type
}];
console.log('File uploaded (processed)', newFiles);
this.setValue(this.getValue().concat(newFiles));
},
filepickerError: function(msg, file) {
console.debug('Filepicker error', msg);
this.$loader.hide();
this.$error.show();
},
filepickerProgress: function(percent, message, publicUrl, file) {
//console.log('Filepicker progress', percent, message);
this.$loader.show();
this.$error.hide();
},
updateList: function(files) {
// this code is currently duplicated as a handlebar helper (I wanted to let this
// backbone-forms field stand on its own)
var displayFilesize = function(bytes) {
// TODO improve this function
return Math.floor(bytes / 1024) + 'K';
};
this.$list.empty();
_(files).each(function(file) {
var a = $('<a>', {
target: '_blank',
href: file.url,
text: file.filename + ' (' + file.content_type + ') ' + displayFilesize(file.size)
});
var li = $('<li>').append(a);
li.append(a, ' ', $('<a href="#" class="remove"><i class="icon-remove"></i></a>').data('url', file.url));
this.$list.append(li);
}, this);
this.$list[files.length ? 'show' : 'hide']();
},
removeFile: function(ev) {
if (ev) ev.preventDefault();
var url = $(ev.currentTarget).data('url');
var files = this.getValue();
this.setValue(_(files).reject(function(one) {
return one.url === url;
}));
}
});
// this version uses the filepicker.io service
editors.FilepickerIO = editors.Text.extend({
tagName: 'div',
events: {
'change input[type=file]': 'uploadFile',
'click .remove': 'removeFile'
},
initialize: function(options) {
_.bindAll(this, 'filepickerSuccess', 'filepickerError', 'filepickerProgress');
editors.Text.prototype.initialize.call(this, options);
this.$input = $('<input type="hidden" name="'+this.key+'" />');
this.$uploadInput = $('<input type="file" multiple="multiple" />');
this.$loader = $('<p class="upload-status"><span class="loader"></span> Uploading&hellip;</p>');
this.$error = $('<p class="upload-error error">Error</p>');
this.$list = $('<ul class="file-list">');
},
// return an array of file dicts
getValue: function() {
var val = this.$input.val();
return val ? JSON.parse(val) : [];
},
setValue: function(value) {
var str, files = value;
if (_(value).isObject()) {
str = JSON.stringify(value);
} else {
files = value ? JSON.parse(value) : [];
}
this.$input.val(str);
this.updateList(files);
},
render: function(options) {
editors.Text.prototype.render.apply(this, arguments);
this.$el.append(this.$input);
this.$el.append(this.$uploadInput);
this.$el.append(this.$loader.hide());
this.$el.append(this.$error.hide());
this.$el.append(this.$list);
return this;
},
uploadFile: function() {
filepicker = require('filepicker-io');
// https://developers.filepicker.io/docs/web/#local-upload
filepicker.uploadFile(this.$uploadInput.get(0), this.filepickerSuccess, this.filepickerError, this.filepickerProgress);
},
filepickerSuccess: function(files) {
console.log('Filepicker (raw)', files);
this.$loader.hide();
this.$error.hide();
this.$uploadInput.val('');
// when uploading one file, it returns just an object
if (!_(files).isArray()) { files = [files]; }
// turn response array into a flatter array of objects
var newFiles = _(files).map(function(one) {
return {
url: one.url,
filename: one.data.filename,
s3_key: one.data.key,
size: one.data.size,
content_type: one.data.type
};
});
console.log('Filepicker (processed)', newFiles);
this.setValue(this.getValue().concat(newFiles));
},
filepickerError: function(msg) {
console.debug('Filepicker error', msg);
this.$loader.hide();
this.$error.show();
},
filepickerProgress: function(percent) {
this.$loader.show();
this.$error.hide();
},
updateList: function(files) {
// this code is currently duplicated as a handlebar helper (I wanted to let this
// backbone-forms field stand on its own)
var displayFilesize = function(bytes) {
// TODO improve this function
return Math.floor(bytes / 1024) + 'K';
};
this.$list.empty();
_(files).each(function(file) {
var a = $('<a>', {
target: '_blank',
href: file.url,
text: file.filename + ' (' + file.content_type + ') ' + displayFilesize(file.size)
});
var li = $('<li>').append(a);
li.append(a, ' ', $('<a href="#" class="remove"><i class="icon-remove"></i></a>').data('s3_key', file.s3_key));
this.$list.append(li);
}, this);
this.$list[files.length ? 'show' : 'hide']();
},
removeFile: function(ev) {
if (ev) ev.preventDefault();
var s3_key = $(ev.currentTarget).data('s3_key');
var files = this.getValue();
this.setValue(_(files).reject(function(one) {
return one.s3_key === s3_key;
}));
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment