file-dropzone
Version:
Drop files anywhere on your web page. Based on jQuery.
324 lines (277 loc) • 7.82 kB
JavaScript
import u from './util'
import $ from 'jquery'
let noop = function () { }
let _files = []
let _setFiles = function (files) {
_files = files
}
let dragTrack = 0
const DEFAULTS = {
target: '',
fileHoverClass: 'dropzone--file-hover',
clickable: true,
multiple: true,
paramName: 'file',
accept: '',
capture: true,
unique: false,
noFolder: true,
forceReplace: false,
onChange: noop,
onEnter: noop,
onLeave: noop,
onHover: noop,
onDrop: noop,
onFolderFound: noop,
onInvalid: noop,
beforeAdd: noop
}
export default class FileDropzone {
constructor(selector, options) {
let opts = options || {}
let target = $(selector)
if (!u.isString(selector) && u.isObject(selector) && !(selector instanceof $) && !(selector instanceof Element)) {
opts = selector || {}
target = $(opts.target)
}
if (!target || target.length <= 0) {
throw new Error('No matched element.')
}
this.element = target
this.options = u.assign(DEFAULTS, opts)
this.disabled = false
this.fileInput = null
this.multiple = false
this.init()
}
static getFileSize(file, unit = 'b') {
let units = ['b', 'kb', 'mb', 'gb', 'tb']
if (!(file instanceof File)) {
throw new TypeError('First argument "file" should be a File instance.')
}
if (!unit || typeof unit !== 'string') {
unit = 'b'
}
if (unit == 'b') return file.size
var unitIndex = units.indexOf(unit.toLowerCase())
if (unitIndex < 0) {
throw new TypeError('The unit should be one of "tb", "gb", "mb", "kb" and "b", the default value is "b"')
}
return file.size / Math.pow(1024, unitIndex)
}
init() {
_setFiles.bind(this)([])
this.clickable = this.options.clickable
if (this.clickable) {
this.enableClick()
}
this.element[0].addEventListener('click', _handleClick.bind(this))
this.multiple = typeof this.options.multiple === 'boolean' ? this.options.multiple : true
this.fileInput = $(`<input type="file" hidden name="${this.options.paramName}" class="${this.options.paramName}" >`)
this.element.next(this.fileInput)
if (this.multiple) {
this.fileInput.attr('multiple', 'multiple')
}
if (this.options.capture) {
this.fileInput.attr('capture', this.options.capture)
}
if (this.options.accept) {
this.fileInput.attr('accept', this.options.accept)
}
this.element.on('dragenter', _handleDragEnter.bind(this))
.on('dragleave', _handleDragLeave.bind(this))
.on('dragend', _handleDragLeave.bind(this))
.on('dragover', _handleDragOver.bind(this))
.on('drop', _handleDrop.bind(this))
var that = this
this.fileInput.on('click', (evt) => {
evt.stopPropagation()
}).on('change', function (evt) {
if (that.disabled) return
var fileList = evt.target.files
var files = []
for (var i = 0, len = fileList.length; i < len; i++) {
let value = fileList[i]
if (value instanceof File) {
files.push(value)
}
}
_addFiles.bind(that)(files)
if (!that.options.unique) {
$(this).val('')
}
})
_insetStyles()
}
getFiles() {
return _files
}
removeFile(arg) {
let files = this.getFiles()
let oldLen = files.length
var fileToRemove
if (arg instanceof File) {
fileToRemove = arg
var newList = u.without(files, fileToRemove)
_setFiles.bind(this)(newList)
} else if (typeof arg === 'number') {
fileToRemove = files.splice(arg, 1)[0]
}
if (this.getFiles().length === oldLen - 1) {
this.options.onChange && this.options.onChange.bind(this)()
return fileToRemove
} else {
return null
}
}
pop() {
let files = this.getFiles()
if (!files.length) {
return null
}
var removed = files.pop()
_setFiles.bind(this)(files)
this.options.onChange && this.options.onChange.bind(this)()
return removed
}
shift() {
let files = this.getFiles()
if (!files.length) {
return null
}
var removed = files.shift()
_setFiles.bind(this)(files)
this.options.onChange && this.options.onChange.bind(this)()
return removed
}
openFileChooser() {
this.fileInput.click()
}
clearAll() {
_setFiles.bind(this)([])
this.options.onChange && this.options.onChange.bind(this)()
}
disable() {
this.disabled = true
this.element.addClass('dropzone--disabled')
this.fileInput.prop('disabled', true)
}
enable() {
this.disabled = false
this.element.removeClass('dropzone--disabled')
this.fileInput.prop('disabled', false)
}
disableClick() {
this.element.removeClass('dropzone--clickable')
this.clickable = false
}
enableClick() {
this.element.addClass('dropzone--clickable')
this.clickable = true
}
}
function _addFiles(files) {
var valid = []
var invalid = []
files.forEach(file => {
if (u.fileTypeValid(file, this.options.accept)) {
if (!this.options.unique || this.getFiles().indexOf(file) < 0) {
valid.push(file)
}
} else {
invalid.push(file)
}
})
if (invalid.length) {
this.options.onInvalid && this.options.onInvalid.bind(this)(invalid)
}
if (!valid[0]) return
if (!this.multiple) {
valid = valid.slice(0, 1)
}
let canAdd = true
if (this.options.beforeAdd) {
let result = this.options.beforeAdd.bind(this)(valid)
if (typeof result == 'boolean') {
canAdd = result
}
}
if (!canAdd) return
if (!this.multiple || this.options.forceReplace) {
_setFiles.bind(this)(valid)
} else {
_setFiles.bind(this)(this.getFiles().concat(valid))
}
this.options.onChange && this.options.onChange.bind(this)()
}
function _insetStyles() {
if (!window.__dropzone_styled_inserted) {
$(`<style>
.dropzone--clickable { cursor: pointer; }
.dropzone--file-hover { box-shadow: inset 0 0 10px #aaa; }
</style>`
).appendTo("head")
window.__dropzone_styled_inserted = true
}
}
function _handleDragEnter(evt) {
dragTrack++
if (this.disabled || !u.eventHasFile(evt)) return
evt.preventDefault()
evt.stopPropagation()
if (dragTrack == 1) {
this.options.onEnter && this.options.onEnter.bind(this)(evt)
this.element.addClass(this.options.fileHoverClass)
}
}
function _handleDragLeave(evt) {
dragTrack--
if (this.disabled || !u.eventHasFile(evt)) return
evt.preventDefault()
evt.stopPropagation()
if (dragTrack === 0) {
this.options.onLeave && this.options.onLeave.bind(this)(evt)
this.element.removeClass(this.options.fileHoverClass)
}
}
function _handleDragOver(evt) {
if (this.disabled || !u.eventHasFile(evt)) return
evt.preventDefault()
// evt.stopPropagation()
this.options.onHover && this.options.onHover.bind(this)(evt)
}
function _handleDrop(evt) {
dragTrack--
if (this.disabled || !u.eventHasFile(evt)) return
evt.preventDefault()
evt.stopPropagation()
this.options.onDrop && this.options.onDrop.bind(this)(evt)
this.element.removeClass(this.options.fileHoverClass)
let files = _detectFolders.bind(this)(u.getFilesFromEvent(evt))
_addFiles.bind(this)(files)
}
function _handleClick(evt) {
if (this.disabled) return
if (!this.clickable) return
this.openFileChooser()
}
function _detectFolders(files) {
var folders = []
var realFiles = []
for (var i = 0, len = files.length; i < len; i++) {
var f = files[i]
if (!f.type && f.size % 4096 == 0) {
folders.push(f)
} else {
realFiles.push(f)
}
}
if (folders.length > 0) {
this.options.onFolderFound && this.options.onFolderFound.bind(this)(folders)
}
if (this.options.noFolder) {
return realFiles
} else {
return files
}
}