UNPKG

wise-web-client

Version:

Based on Spine MVC framework

490 lines (428 loc) 17.5 kB
define(['jquery', 'backbone', 'handlebars', './BaseView', 'text!../../../tpl/upload_manager_file.hbr', 'text!../../../tpl/upload_manager_main.hbr', 'jquery.fileupload' ], function($, Backbone, Handlebars, BaseView, fileTemplate, mainTemplate) { var context = {}; var mc = { /** * This model represents a file. * */ File: Backbone.Model.extend({ state: "pending", url: function() { return this.id ? '/api/v1/file/' + this.id : '/api/v1/file/'; }, /** * Start upload. * */ start: function() { if (this.isPending()) { this.get('processor').submit(); this.state = "running"; // Dispatch event this.trigger('filestarted', this); } }, /** * Cancel a file upload. * */ cancel: function() { this.get('processor').abort(); this.destroy(); // Dispatch event this.state = "canceled"; this.trigger('filecanceled', this); }, /** * Notify file that progress updated. * */ progress: function(data) { // Dispatch event this.trigger('fileprogress', this.get('processor').progress()); }, /** * Notify file that upload failed. * */ fail: function(error) { // Dispatch event this.state = "error"; this.trigger('filefailed', error); }, /** * Notify file that upload is done. * */ done: function(result) { // Dispatch event this.state = "error"; this.trigger('filedone', result); }, /** * Is this file pending to be uploaded ? * */ isPending: function() { return this.getState() == "pending"; }, /** * Is this file currently uploading ? * */ isRunning: function() { return this.getState() == "running"; }, /** * Is this file uploaded ? * */ isDone: function() { return this.getState() == "done"; }, /** * Is this upload in error ? * */ isError: function() { return this.getState() == "error" || this.getState == "canceled"; }, /** * Get the file state. * */ getState: function() { return this.state; } }), /** * This is a file collection, used to manage the selected * and processing files. * */ FileCollection: Backbone.Collection.extend({ model: this.File }), /** * A file view, which is the view that manage a single file * process in the upload manager. * */ FileView: BaseView.extend({ className: 'upload-manager-file row-fluid', tagName: 'div', template: Handlebars.compile(fileTemplate), initialize: function(options) { this.processUploadMsg = options.processUploadMsg; this.doneMsg = options.doneMsg; // Bind model events this.model.on('destroy', this.remove, this); this.model.on('fileprogress', this.updateProgress, this); this.model.on('filefailed', this.hasFailed, this); this.model.on('filedone', this.hasDone, this); this.model.on('destroy', this.hasDestroy, this); // In each case, update view this.model.on('all', this.update, this); }, /** * Render the file item view. * */ render: function() { $(this.el).html(this.template(this.computeData())); // Bind events this.bindEvents(); // Update elements this.update(); return this; }, /** * Update upload progress. * */ updateProgress: function(progress) { var percent = parseInt(progress.loaded / progress.total * 100, 10); var progressHTML = percent + '%'; if (percent >= 100 && this.processUploadMsg) { progressHTML = this.processUploadMsg; } $('div.progress', this.el) .find('.bar') .css('width', percent + '%') .parent() .find('.progress-label') .html(progressHTML); }, /** * File upload has failed. * */ hasFailed: function(error) { $('span.message', this.el).html('<i class="fa fa-exclamation-triangle"></i> ' + error); }, /** * File upload is done. * */ hasDone: function(result) { this.model.set('id', result._id); $('span.message', this.el).html('<i class="fa fa-check"></i> ' + (this.doneMsg || 'Uploaded')); this.pubsub.trigger('file:uploadFinish', { id: result._id, context: context }); var fileName = $('span.name', this.el).html(); $('span.name', this.el).html('<a href="' + this.model.url() + '">' + fileName + '</a>'); }, /** * File delete/cancel is done. * */ hasDestroy: function(file) { this.pubsub.trigger('file:destroyFinish', { id: file.id, context: context }); }, /** * Update view without complete rendering. * */ update: function() { var when_pending = $('span.size, button#btn-cancel', this.el), when_running = $('div.progress, button#btn-cancel', this.el), when_done = $('span.message, button#btn-clear', this.el); if (this.model.isPending()) { when_running.add(when_done).addClass('hidden'); when_pending.removeClass('hidden'); } else if (this.model.isRunning()) { when_pending.add(when_done).addClass('hidden'); when_running.removeClass('hidden'); } else if (this.model.isDone() || this.model.isError()) { when_pending.add(when_running).addClass('hidden'); when_done.removeClass('hidden'); } }, /** * Bind local elements events. * */ bindEvents: function() { var self = this; // DOM events $('button#btn-cancel', this.el).click(function() { self.model.cancel(); }); $('button#btn-clear', this.el).click(function() { self.model.destroy(); }); }, /** * Compute data to be passed to the view. * */ computeData: function() { return $.extend({}, this.model.get('data')); } }) }; var View = BaseView.extend({ /** * Default options, that will be merged with the passed. * */ defaults: { uploadUrl: '/upload', autoUpload: false, fileUploadId: 'fileupload', startUploadsId: 'start-uploads-button', cancelUploadsId: 'cancel-uploads-button', dataType: 'json' }, el: 'div.file-uploader', /** * An integer used to track the files by a unique * identifier. * */ template: Handlebars.compile(mainTemplate), file_id: 0, /** * View container class. * */ className: 'container-fluid upload-manager', /** * Initialize upload manager options * */ initialize: function(options) { // Merge options this.options = $.extend(this.defaults, options); // Create the file list this.files = new mc.FileCollection(); // set global context context = this.options.context || {}; // Create the file-upload wrapper this.uploadProcess = $('<input id="' + this.options.fileUploadId + '" type="file" name="files[]" multiple="multiple">').fileupload({ dataType: this.options.dataType, url: this.options.uploadUrl, formData: this.options.formData, autoUpload: this.options.autoUpload, singleFileUploads: true }); // Add upload process events handlers this.bindProcessEvents(); // Add local events handlers this.bindLocal(); //add a handlebar helper Handlebars.registerHelper('filesize', function(bytes) { var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes === 0) return '0 Bytes'; var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i]; }); }, /** * Bind local events. * */ bindLocal: function() { var self = this; this.on('fileadd', function(file) { // Add it to current list self.files.add(file); // Create the view self.renderFile(file); }).on('fileprogress', function(file, progress) { file.progress(progress); }).on('filefail', function(file, error) { file.fail(error); }).on('filedone', function(file, data) { file.done(data.result); }); // When collection changes this.files.on('all', this.update, this); }, /** * Render a file. * */ renderFile: function(file) { var file_view = new mc.FileView($.extend(this.options, { model: file })); $('div#file-list').append(file_view.render().$el); }, /** * Update the view without full rendering. * */ update: function() { var with_files_elements = $('button#' + this.options.cancelUploadsId + ', button#' + this.options.startUploadsId, this.el); var without_files_elements = $('#file-list .no-data', this.el); if (this.files.length > 0) { with_files_elements.removeClass('hidden'); without_files_elements.addClass('hidden'); } else { with_files_elements.addClass('hidden'); without_files_elements.removeClass('hidden'); } }, /** * Bind events on the upload processor. * */ bindProcessEvents: function() { var self = this; this.uploadProcess.on('fileuploadadd', function(e, data) { // Create an array in which the file objects // will be stored. data.uploadManagerFiles = []; // A file is added, process for each file. // Note: every times, the data.files array length is 1 because // of "singleFileUploads" option. $.each(data.files, function(index, file_data) { // Create the file object file_data.id = self.file_id++; var file = new mc.File({ id: file_data.id, name: file_data.name, data: file_data, processor: data }); // Add file in data data.uploadManagerFiles.push(file); // Trigger event self.trigger('fileadd', file); }); }).on('fileuploadprogress', function(e, data) { $.each(data.uploadManagerFiles, function(index, file) { self.trigger('fileprogress', file, data); }); }).on('fileuploadfail', function(e, data) { $.each(data.uploadManagerFiles, function(index, file) { var error = "Unknown error"; if (typeof data.errorThrown == "string") { error = data.errorThrown; } else if (typeof data.errorThrown == "object") { error = data.errorThrown.message; } else if (data.result) { if (data.result.error) { error = data.result.error; } else if (data.result.files && data.result.files[index] && data.result.files[index].error) { error = data.result.files[index].error; } else { error = "Unknown remote error"; } } self.trigger('filefail', file, error); }); }).on('fileuploaddone', function(e, data) { $.each(data.uploadManagerFiles, function(index, file) { self.trigger('filedone', file, data); }); }); }, /** * Render the main part of upload manager. * */ render: function() { this.$el.html(this.template()); // Update view this.update(); // Add add files handler var input = $('input#' + this.options.fileUploadId, this.el), self = this; input.on('change', function() { self.uploadProcess.fileupload('add', { fileInput: $(this) }); }); // Add cancel all handler $('button#' + this.options.cancelUploadsId, this.el).click(function() { while (self.files.length) { self.files.at(0).cancel(); } }); // Add start uploads handler $('button#' + this.options.startUploadsId, this.el).click(function() { self.files.each(function(file) { file.start(); }); }); // Render current files $.each(this.files, function(i, file) { self.renderFile(file); }); return this; } }); return View; } );