hazdev-webutils
Version:
Utilities commonly used in web applications developed by the EHP HazDev team.
481 lines (380 loc) • 11.1 kB
JavaScript
;
var Collection = require('mvc/Collection'),
CollectionView = require('mvc/CollectionView'),
ModalView = require('mvc/ModalView'),
Model = require('mvc/Model'),
View = require('mvc/View'),
FileIO = require('util/FileIO'),
Message = require('util/Message'),
Util = require('util/Util');
var _FILE_SIZE_SUFFIXES = ['B', 'KB', 'MB', 'GB'];
var _DEFAULT_INTRO_TEXT = [
'You may “Browse” for files, or drag-and-drop files using the ',
'area below. Once you have added files, you may preview them by clicking ',
'the blue file name. Preview behavior is browser-dependent. If you select ',
'a file with the same name more than once, then only the most recent ',
'selection will be used. Directories are not supported.'
].join('');
// Default values to be used by constructor
var _DEFAULTS = {
browseText: 'Browse',
cancelCallback: function () {},
cancelText: 'Cancel',
cancelTooltip: 'Cancel',
dropzoneText: 'Drag & drop file(s) here…',
intro: {
text: _DEFAULT_INTRO_TEXT,
classes: 'alert info'
},
title: 'File Input',
uploadCallback: function () {},
uploadText: 'Upload',
uploadTooltip: 'Upload file(s)'
};
/**
* Private inner class. This is a view for rendering individual files in
* a list-type format. It is provided to the CollectionView as the factory.
*
*/
var FileView = function (params) {
var _this,
_initialize,
_collection,
_delete,
_fileName,
_fileSize,
_bindEventListeners,
_createViewSkeleton,
_formatFileSize,
_onDeleteClick,
_unbindEventListeners;
_this = View(params);
_initialize = function (/*params*/) {
_collection = params.collection;
_createViewSkeleton();
_bindEventListeners();
_this.render();
};
_bindEventListeners = function () {
_delete.addEventListener('click', _onDeleteClick);
};
_createViewSkeleton = function () {
_this.el.classList.add('file-view');
_this.el.innerHTML = [
'<span class="file-view-label">',
'<a href="javascript:void(null);" target="_blank" ',
'class="file-view-name"></a>',
'<span class="file-view-size"></span>',
'</span>',
'<a href="javascript:void(null);" class="file-view-delete" ',
'title="Delete File">×</a>'
].join('');
_fileName = _this.el.querySelector('.file-view-name');
_fileSize = _this.el.querySelector('.file-view-size');
_delete = _this.el.querySelector('.file-view-delete');
};
_formatFileSize = function (size) {
var numDecimals,
suffixIndex;
numDecimals = 0;
suffixIndex = 0;
while (size > 1024 && suffixIndex < _FILE_SIZE_SUFFIXES.length) {
size /= 1024;
suffixIndex++;
}
if (size - parseInt(size, 10) !== 0) {
numDecimals = 1;
}
return size.toFixed(numDecimals) + _FILE_SIZE_SUFFIXES[suffixIndex];
};
_onDeleteClick = function () {
if (_collection) {
_collection.remove(_this.model);
}
_this.destroy();
};
_unbindEventListeners = function () {
_delete.removeEventListener('click', _onDeleteClick);
};
_this.destroy = Util.compose(function () {
_unbindEventListeners();
_collection = null;
_delete = null;
_fileName = null;
_fileSize = null;
_bindEventListeners = null;
_createViewSkeleton = null;
_formatFileSize = null;
_onDeleteClick = null;
_unbindEventListeners = null;
_initialize = null;
_this = null;
}, _this.destroy);
_this.render = function () {
var file;
file = _this.model.get('file');
_fileName.innerHTML = file.name;
_fileName.setAttribute('title', file.name);
_fileName.setAttribute('href', _this.model.get('url'));
_fileName.setAttribute('download', file.name);
_fileSize.innerHTML = '(' + _formatFileSize(file.size) + ')';
};
_initialize(params);
params = null;
return _this;
};
/**
* Class: FileInputView
*
* @param params {Object}
* Configuration options. See _DEFAULTS for more details.
*/
var FileInputView = function (params) {
var _this,
_initialize,
_browseButton,
_cancelCallback,
_collection,
_collectionView,
_dropzone,
_fileInput,
_filesContainer,
_io,
_messageContainer,
_modal,
_uploadCallback,
_bindEventListeners,
_createViewSkeleton,
_handleFile,
_onBrowseClick,
_onCancelClick,
_onDragLeave,
_onDragOver,
_onDrop,
_onReadError,
_onReadSuccess,
_onUploadClick,
_showError,
_unbindEventListeners;
// Inherit from parent class
_this = View(params);
/**
* @constructor
*
*/
_initialize = function (params) {
// Enumerate each property expected to be given in params method
params = Util.extend({}, _DEFAULTS, params);
_collection = Collection([]);
_io = FileIO();
_uploadCallback = params.uploadCallback;
_cancelCallback = params.cancelCallback;
_createViewSkeleton(params);
_bindEventListeners();
_modal = ModalView(_this.el, {
title: params.title,
buttons: [
{
text: params.uploadText,
title: params.uploadTooltip,
callback: _onUploadClick
},
{
text: params.cancelText,
title: params.cancelTooltip,
callback: _onCancelClick
}
],
classes: ['file-input']
});
};
_bindEventListeners = function () {
_dropzone.addEventListener('dragleave', _onDragLeave);
_dropzone.addEventListener('dragover', _onDragOver);
_dropzone.addEventListener('drop', _onDrop);
_browseButton.addEventListener('click', _onBrowseClick);
_fileInput.addEventListener('change', _onDrop);
};
_createViewSkeleton = function (params) {
var intro;
_this.el.innerHTML = [
'<input type="file" class="file-input-input" multiple/>',
'<div class="file-input-dropzone">',
'<span class="file-input-dropzone-text">',
params.dropzoneText,
'</span>',
'<button class="file-input-browse-button">',
params.browseText,
'</button>',
'</div>',
'<div class="file-input-messages"></div>',
'<ul class="file-input-files no-style"></ul>'
].join('');
if (params.intro) {
if (params.intro instanceof Node) {
intro = params.intro;
} else if (typeof params.intro === 'string') {
intro = document.createElement('div');
intro.innerHTML = params.intro;
} else {
intro = document.createElement('div');
intro.innerHTML = params.intro.text || '';
if (params.intro.classes) {
intro.className = params.intro.classes;
}
}
intro.classList.add('file-input-intro');
_this.el.insertBefore(intro, _this.el.firstChild);
}
_dropzone = _this.el.querySelector('.file-input-dropzone');
_browseButton = _this.el.querySelector('.file-input-browse-button');
_fileInput = _this.el.querySelector('.file-input-input');
_filesContainer = _this.el.querySelector('.file-input-files');
_messageContainer = _this.el.querySelector('.file-input-messages');
_collectionView = CollectionView({
collection: _collection,
el: _filesContainer,
factory: FileView
});
};
_handleFile = function (file) {
try {
// Get the data URL
_io.read({
file: file,
success: _onReadSuccess,
error: _onReadError
});
} catch (e) {
_showError(e.message);
}
};
_onBrowseClick = function () {
var evt;
if (_fileInput.click) {
_fileInput.click();
} else {
evt = document.createEvent('HTMLEvents');
evt.initEvent('click', true, true);
_fileInput.dispatchEvent(evt);
}
};
_onCancelClick = function () {
_cancelCallback();
_this.hide();
};
_onDragLeave = function (e) {
e.preventDefault();
_dropzone.classList.remove('drag-over');
};
_onDragOver = function (e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
_dropzone.classList.add('drag-over');
};
_onDrop = function (e) {
var files,
i;
_onDragLeave(e);
files = e.target.files || e.dataTransfer.files;
for (i = 0; i < files.length; i++) {
_handleFile(files[i]);
}
_fileInput.value = '';
};
_onReadError = function (params) {
var error;
error = [
'An error occurred reading “', params.file.name, '”.',
'<br/>',
'<small>', params.error.message, '</small>'
].join('');
_showError(error);
};
_onReadSuccess = function (params) {
var oldFile;
params.id = params.file.name;
try {
oldFile = _collection.get(params.id);
if (oldFile) {
oldFile.set(params);
_showError(
'A file name “' + params.file.name + '” was already ' +
'selected. That file has been replaced by this file. To load both ' +
'files, please rename one of the files.', 'info'
);
} else {
_collection.add(Model(params));
}
} catch (e) {
_showError(e.message);
}
};
_onUploadClick = function () {
_uploadCallback(_collection.data().slice(0));
_this.hide();
};
_showError = function (error, type) {
Message({
container: _messageContainer,
content: error,
autoclose: false,
classes: [type || 'error']
});
};
_unbindEventListeners = function () {
_dropzone.removeEventListener('dragleave', _onDragLeave);
_dropzone.removeEventListener('dragover', _onDragOver);
_dropzone.removeEventListener('drop', _onDrop);
_browseButton.removeEventListener('click', _onBrowseClick);
_fileInput.removeEventListener('change', _onDrop);
};
_this.destroy = Util.compose(function () {
_unbindEventListeners();
_collectionView.destroy();
_collection.destroy();
_io.destroy();
_modal.destroy();
_browseButton = null;
_cancelCallback = null;
_collection = null;
_collectionView = null;
_dropzone = null;
_fileInput = null;
_filesContainer = null;
_io = null;
_messageContainer = null;
_modal = null;
_uploadCallback = null;
_bindEventListeners = null;
_createViewSkeleton = null;
_handleFile = null;
_onBrowseClick = null;
_onCancelClick = null;
_onDragLeave = null;
_onDragOver = null;
_onDrop = null;
_onReadError = null;
_onReadSuccess = null;
_onUploadClick = null;
_showError = null;
_unbindEventListeners = null;
_initialize = null;
_this = null;
}, _this.destroy);
_this.hide = function () {
_modal.hide();
};
_this.show = function (clean) {
if (clean) {
_collection.reset([]);
_messageContainer.innerHTML = '';
}
_modal.show();
};
// Always call the constructor
_initialize(params);
params = null;
return _this;
};
module.exports = FileInputView;