1441 lines
48 KiB
CoffeeScript
1441 lines
48 KiB
CoffeeScript
###
|
|
#
|
|
# More info at [www.dropzonejs.com](http://www.dropzonejs.com)
|
|
#
|
|
# Copyright (c) 2012, Matias Meno
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
#
|
|
###
|
|
|
|
|
|
# Dependencies
|
|
Em = Emitter ? require "emitter" # Can't be the same name because it will lead to a local variable
|
|
|
|
noop = ->
|
|
|
|
class Dropzone extends Em
|
|
|
|
###
|
|
This is a list of all available events you can register on a dropzone object.
|
|
|
|
You can register an event handler like this:
|
|
|
|
dropzone.on("dragEnter", function() { });
|
|
|
|
###
|
|
events: [
|
|
"drop"
|
|
"dragstart"
|
|
"dragend"
|
|
"dragenter"
|
|
"dragover"
|
|
"dragleave"
|
|
"addedfile"
|
|
"removedfile"
|
|
"thumbnail"
|
|
"error"
|
|
"errormultiple"
|
|
"processing"
|
|
"processingmultiple"
|
|
"uploadprogress"
|
|
"totaluploadprogress"
|
|
"sending"
|
|
"sendingmultiple"
|
|
"success"
|
|
"successmultiple"
|
|
"canceled"
|
|
"canceledmultiple"
|
|
"complete"
|
|
"completemultiple"
|
|
"reset"
|
|
"maxfilesexceeded"
|
|
"maxfilesreached"
|
|
]
|
|
|
|
|
|
|
|
defaultOptions:
|
|
url: null
|
|
method: "post"
|
|
withCredentials: no
|
|
parallelUploads: 2
|
|
uploadMultiple: no # Whether to send multiple files in one request.
|
|
maxFilesize: 256 # in MB
|
|
paramName: "file" # The name of the file param that gets transferred.
|
|
createImageThumbnails: true
|
|
maxThumbnailFilesize: 10 # in MB. When the filename exceeds this limit, the thumbnail will not be generated.
|
|
thumbnailWidth: 100
|
|
thumbnailHeight: 100
|
|
|
|
# Can be used to limit the maximum number of files that will be handled
|
|
# by this Dropzone
|
|
maxFiles: null
|
|
|
|
# Can be an object of additional parameters to transfer to the server.
|
|
# This is the same as adding hidden input fields in the form element.
|
|
params: { }
|
|
|
|
# If true, the dropzone will present a file selector when clicked.
|
|
clickable: yes
|
|
|
|
# Whether hidden files in directories should be ignored.
|
|
ignoreHiddenFiles: yes
|
|
|
|
# You can set accepted mime types here.
|
|
#
|
|
# The default implementation of the `accept()` function will check this
|
|
# property, and if the Dropzone is clickable this will be used as
|
|
# `accept` attribute.
|
|
#
|
|
# This is a comma separated list of mime types or extensions. E.g.:
|
|
#
|
|
# audio/*,video/*,image/png,.pdf
|
|
#
|
|
# See https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept
|
|
# for a reference.
|
|
acceptedFiles: null
|
|
|
|
# @deprecated
|
|
# Use acceptedFiles instead.
|
|
acceptedMimeTypes: null
|
|
|
|
# If false, files will be added to the queue but the queue will not be
|
|
# processed automatically.
|
|
# This can be useful if you need some additional user input before sending
|
|
# files (or if you want want all files sent at once).
|
|
# If you're ready to send the file simply call myDropzone.processQueue()
|
|
autoProcessQueue: on
|
|
|
|
# If false, files added to the dropzone will not be queued by default.
|
|
# You'll have to call `enqueueFile(file)` manually.
|
|
autoQueue: on
|
|
|
|
# If true, Dropzone will add a link to each file preview to cancel/remove
|
|
# the upload.
|
|
# See dictCancelUpload and dictRemoveFile to use different words.
|
|
addRemoveLinks: no
|
|
|
|
# A CSS selector or HTML element for the file previews container.
|
|
# If null, the dropzone element itself will be used.
|
|
# If false, previews won't be rendered.
|
|
previewsContainer: null
|
|
|
|
|
|
# Dictionary
|
|
|
|
# The text used before any files are dropped
|
|
dictDefaultMessage: "Drop files here to upload"
|
|
|
|
# The text that replaces the default message text it the browser is not supported
|
|
dictFallbackMessage: "Your browser does not support drag'n'drop file uploads."
|
|
|
|
# The text that will be added before the fallback form
|
|
# If null, no text will be added at all.
|
|
dictFallbackText: "Please use the fallback form below to upload your files like in the olden days."
|
|
|
|
# If the filesize is too big.
|
|
dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB."
|
|
|
|
# If the file doesn't match the file type.
|
|
dictInvalidFileType: "You can't upload files of this type."
|
|
|
|
# If the server response was invalid.
|
|
dictResponseError: "Server responded with {{statusCode}} code."
|
|
|
|
# If used, the text to be used for the cancel upload link.
|
|
dictCancelUpload: "Cancel upload"
|
|
|
|
# If used, the text to be used for confirmation when cancelling upload.
|
|
dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?"
|
|
|
|
# If used, the text to be used to remove a file.
|
|
dictRemoveFile: "Remove file"
|
|
|
|
# If this is not null, then the user will be prompted before removing a file.
|
|
dictRemoveFileConfirmation: null
|
|
|
|
# Displayed when the maxFiles have been exceeded
|
|
# You can use {{maxFiles}} here, which will be replaced by the option.
|
|
dictMaxFilesExceeded: "You can not upload any more files."
|
|
|
|
|
|
# If `done()` is called without argument the file is accepted
|
|
# If you call it with an error message, the file is rejected
|
|
# (This allows for asynchronous validation).
|
|
accept: (file, done) -> done()
|
|
|
|
|
|
# Called when dropzone initialized
|
|
# You can add event listeners here
|
|
init: -> noop
|
|
|
|
# Used to debug dropzone and force the fallback form.
|
|
forceFallback: off
|
|
|
|
# Called when the browser does not support drag and drop
|
|
fallback: ->
|
|
# This code should pass in IE7... :(
|
|
@element.className = "#{@element.className} dz-browser-not-supported"
|
|
|
|
for child in @element.getElementsByTagName "div"
|
|
if /(^| )dz-message($| )/.test child.className
|
|
messageElement = child
|
|
child.className = "dz-message" # Removes the 'dz-default' class
|
|
continue
|
|
unless messageElement
|
|
messageElement = Dropzone.createElement """<div class="dz-message"><span></span></div>"""
|
|
@element.appendChild messageElement
|
|
|
|
span = messageElement.getElementsByTagName("span")[0]
|
|
span.textContent = @options.dictFallbackMessage if span
|
|
|
|
@element.appendChild @getFallbackForm()
|
|
|
|
|
|
|
|
# Gets called to calculate the thumbnail dimensions.
|
|
#
|
|
# You can use file.width, file.height, options.thumbnailWidth and
|
|
# options.thumbnailHeight to calculate the dimensions.
|
|
#
|
|
# The dimensions are going to be used like this:
|
|
#
|
|
# var info = @options.resize.call(this, file);
|
|
# ctx.drawImage(img, info.srcX, info.srcY, info.srcWidth, info.srcHeight, info.trgX, info.trgY, info.trgWidth, info.trgHeight);
|
|
#
|
|
# srcX, srcy, trgX and trgY can be omitted (in which case 0 is assumed).
|
|
# trgWidth and trgHeight can be omitted (in which case the options.thumbnailWidth / options.thumbnailHeight are used)
|
|
resize: (file) ->
|
|
info =
|
|
srcX: 0
|
|
srcY: 0
|
|
srcWidth: file.width
|
|
srcHeight: file.height
|
|
|
|
srcRatio = file.width / file.height
|
|
|
|
info.optWidth = @options.thumbnailWidth
|
|
info.optHeight = @options.thumbnailHeight
|
|
|
|
# automatically calculate dimensions if not specified
|
|
if !info.optWidth? and !info.optHeight?
|
|
info.optWidth = info.srcWidth
|
|
info.optHeight = info.srcHeight
|
|
else if !info.optWidth?
|
|
info.optWidth = srcRatio * info.optHeight
|
|
else if !info.optHeight?
|
|
info.optHeight = (1/srcRatio) * info.optWidth
|
|
|
|
trgRatio = info.optWidth / info.optHeight
|
|
|
|
if file.height < info.optHeight or file.width < info.optWidth
|
|
# This image is smaller than the canvas
|
|
info.trgHeight = info.srcHeight
|
|
info.trgWidth = info.srcWidth
|
|
|
|
else
|
|
# Image is bigger and needs rescaling
|
|
if srcRatio > trgRatio
|
|
info.srcHeight = file.height
|
|
info.srcWidth = info.srcHeight * trgRatio
|
|
else
|
|
info.srcWidth = file.width
|
|
info.srcHeight = info.srcWidth / trgRatio
|
|
|
|
info.srcX = (file.width - info.srcWidth) / 2
|
|
info.srcY = (file.height - info.srcHeight) / 2
|
|
|
|
return info
|
|
|
|
|
|
###
|
|
Those functions register themselves to the events on init and handle all
|
|
the user interface specific stuff. Overwriting them won't break the upload
|
|
but can break the way it's displayed.
|
|
You can overwrite them if you don't like the default behavior. If you just
|
|
want to add an additional event handler, register it on the dropzone object
|
|
and don't overwrite those options.
|
|
###
|
|
|
|
|
|
|
|
|
|
# Those are self explanatory and simply concern the DragnDrop.
|
|
drop: (e) -> @element.classList.remove "dz-drag-hover"
|
|
dragstart: noop
|
|
dragend: (e) -> @element.classList.remove "dz-drag-hover"
|
|
dragenter: (e) -> @element.classList.add "dz-drag-hover"
|
|
dragover: (e) -> @element.classList.add "dz-drag-hover"
|
|
dragleave: (e) -> @element.classList.remove "dz-drag-hover"
|
|
|
|
paste: noop
|
|
|
|
# Called whenever there are no files left in the dropzone anymore, and the
|
|
# dropzone should be displayed as if in the initial state.
|
|
reset: ->
|
|
@element.classList.remove "dz-started"
|
|
|
|
# Called when a file is added to the queue
|
|
# Receives `file`
|
|
addedfile: (file) ->
|
|
@element.classList.add "dz-started" if @element == @previewsContainer
|
|
|
|
if @previewsContainer
|
|
file.previewElement = Dropzone.createElement @options.previewTemplate.trim()
|
|
file.previewTemplate = file.previewElement # Backwards compatibility
|
|
|
|
@previewsContainer.appendChild file.previewElement
|
|
node.textContent = file.name for node in file.previewElement.querySelectorAll("[data-dz-name]")
|
|
node.innerHTML = @filesize file.size for node in file.previewElement.querySelectorAll("[data-dz-size]")
|
|
|
|
if @options.addRemoveLinks
|
|
file._removeLink = Dropzone.createElement """<a class="dz-remove" href="javascript:undefined;" data-dz-remove>#{@options.dictRemoveFile}</a>"""
|
|
file.previewElement.appendChild file._removeLink
|
|
|
|
removeFileEvent = (e) =>
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
if file.status == Dropzone.UPLOADING
|
|
Dropzone.confirm @options.dictCancelUploadConfirmation, => @removeFile file
|
|
else
|
|
if @options.dictRemoveFileConfirmation
|
|
Dropzone.confirm @options.dictRemoveFileConfirmation, => @removeFile file
|
|
else
|
|
@removeFile file
|
|
|
|
removeLink.addEventListener "click", removeFileEvent for removeLink in file.previewElement.querySelectorAll("[data-dz-remove]")
|
|
|
|
|
|
# Called whenever a file is removed.
|
|
removedfile: (file) ->
|
|
file.previewElement?.parentNode.removeChild file.previewElement if file.previewElement
|
|
@_updateMaxFilesReachedClass()
|
|
|
|
# Called when a thumbnail has been generated
|
|
# Receives `file` and `dataUrl`
|
|
thumbnail: (file, dataUrl) ->
|
|
if file.previewElement
|
|
file.previewElement.classList.remove "dz-file-preview"
|
|
file.previewElement.classList.add "dz-image-preview"
|
|
for thumbnailElement in file.previewElement.querySelectorAll("[data-dz-thumbnail]")
|
|
thumbnailElement.alt = file.name
|
|
thumbnailElement.src = dataUrl
|
|
|
|
|
|
# Called whenever an error occurs
|
|
# Receives `file` and `message`
|
|
error: (file, message) ->
|
|
if file.previewElement
|
|
file.previewElement.classList.add "dz-error"
|
|
message = message.error if typeof message != "String" and message.error
|
|
node.textContent = message for node in file.previewElement.querySelectorAll("[data-dz-errormessage]")
|
|
|
|
errormultiple: noop
|
|
|
|
# Called when a file gets processed. Since there is a cue, not all added
|
|
# files are processed immediately.
|
|
# Receives `file`
|
|
processing: (file) ->
|
|
if file.previewElement
|
|
file.previewElement.classList.add "dz-processing"
|
|
file._removeLink.textContent = @options.dictCancelUpload if file._removeLink
|
|
|
|
processingmultiple: noop
|
|
|
|
# Called whenever the upload progress gets updated.
|
|
# Receives `file`, `progress` (percentage 0-100) and `bytesSent`.
|
|
# To get the total number of bytes of the file, use `file.size`
|
|
uploadprogress: (file, progress, bytesSent) ->
|
|
if file.previewElement
|
|
node.style.width = "#{progress}%" for node in file.previewElement.querySelectorAll("[data-dz-uploadprogress]")
|
|
|
|
# Called whenever the total upload progress gets updated.
|
|
# Called with totalUploadProgress (0-100), totalBytes and totalBytesSent
|
|
totaluploadprogress: noop
|
|
|
|
# Called just before the file is sent. Gets the `xhr` object as second
|
|
# parameter, so you can modify it (for example to add a CSRF token) and a
|
|
# `formData` object to add additional information.
|
|
sending: noop
|
|
|
|
sendingmultiple: noop
|
|
|
|
# When the complete upload is finished and successfull
|
|
# Receives `file`
|
|
success: (file) ->
|
|
if file.previewElement
|
|
file.previewElement.classList.add "dz-success"
|
|
|
|
successmultiple: noop
|
|
|
|
# When the upload is canceled.
|
|
canceled: (file) -> @emit "error", file, "Upload canceled."
|
|
|
|
canceledmultiple: noop
|
|
|
|
# When the upload is finished, either with success or an error.
|
|
# Receives `file`
|
|
complete: (file) ->
|
|
file._removeLink.textContent = @options.dictRemoveFile if file._removeLink
|
|
|
|
completemultiple: noop
|
|
|
|
maxfilesexceeded: noop
|
|
|
|
maxfilesreached: noop
|
|
|
|
|
|
|
|
# This template will be chosen when a new file is dropped.
|
|
previewTemplate: """
|
|
<div class="dz-preview dz-file-preview">
|
|
<div class="dz-details">
|
|
<div class="dz-filename"><span data-dz-name></span></div>
|
|
<div class="dz-size" data-dz-size></div>
|
|
<img data-dz-thumbnail />
|
|
</div>
|
|
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
|
|
<div class="dz-success-mark"><span>✔</span></div>
|
|
<div class="dz-error-mark"><span>✘</span></div>
|
|
<div class="dz-error-message"><span data-dz-errormessage></span></div>
|
|
</div>
|
|
"""
|
|
|
|
# global utility
|
|
extend = (target, objects...) ->
|
|
for object in objects
|
|
target[key] = val for key, val of object
|
|
target
|
|
|
|
constructor: (@element, options) ->
|
|
# For backwards compatibility since the version was in the prototype previously
|
|
@version = Dropzone.version
|
|
|
|
@defaultOptions.previewTemplate = @defaultOptions.previewTemplate.replace /\n*/g, ""
|
|
|
|
@clickableElements = [ ]
|
|
@listeners = [ ]
|
|
@files = [] # All files
|
|
|
|
@element = document.querySelector @element if typeof @element == "string"
|
|
|
|
# Not checking if instance of HTMLElement or Element since IE9 is extremely weird.
|
|
throw new Error "Invalid dropzone element." unless @element and @element.nodeType?
|
|
|
|
throw new Error "Dropzone already attached." if @element.dropzone
|
|
|
|
# Now add this dropzone to the instances.
|
|
Dropzone.instances.push @
|
|
|
|
# Put the dropzone inside the element itself.
|
|
@element.dropzone = @
|
|
|
|
elementOptions = Dropzone.optionsForElement(@element) ? { }
|
|
|
|
@options = extend { }, @defaultOptions, elementOptions, options ? { }
|
|
|
|
# If the browser failed, just call the fallback and leave
|
|
return @options.fallback.call this if @options.forceFallback or !Dropzone.isBrowserSupported()
|
|
|
|
# @options.url = @element.getAttribute "action" unless @options.url?
|
|
@options.url = @element.getAttribute "action" unless @options.url?
|
|
|
|
throw new Error "No URL provided." unless @options.url
|
|
|
|
throw new Error "You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated." if @options.acceptedFiles and @options.acceptedMimeTypes
|
|
|
|
# Backwards compatibility
|
|
if @options.acceptedMimeTypes
|
|
@options.acceptedFiles = @options.acceptedMimeTypes
|
|
delete @options.acceptedMimeTypes
|
|
|
|
@options.method = @options.method.toUpperCase()
|
|
|
|
if (fallback = @getExistingFallback()) and fallback.parentNode
|
|
# Remove the fallback
|
|
fallback.parentNode.removeChild fallback
|
|
|
|
# Display previews in the previewsContainer element or the Dropzone element unless explicitly set to false
|
|
if @options.previewsContainer != false
|
|
if @options.previewsContainer
|
|
@previewsContainer = Dropzone.getElement @options.previewsContainer, "previewsContainer"
|
|
else
|
|
@previewsContainer = @element
|
|
|
|
if @options.clickable
|
|
if @options.clickable == yes
|
|
@clickableElements = [ @element ]
|
|
else
|
|
@clickableElements = Dropzone.getElements @options.clickable, "clickable"
|
|
|
|
|
|
@init()
|
|
|
|
|
|
# Returns all files that have been accepted
|
|
getAcceptedFiles: -> file for file in @files when file.accepted
|
|
|
|
# Returns all files that have been rejected
|
|
# Not sure when that's going to be useful, but added for completeness.
|
|
getRejectedFiles: -> file for file in @files when not file.accepted
|
|
|
|
getFilesWithStatus: (status) -> file for file in @files when file.status == status
|
|
|
|
# Returns all files that are in the queue
|
|
getQueuedFiles: -> @getFilesWithStatus Dropzone.QUEUED
|
|
|
|
getUploadingFiles: -> @getFilesWithStatus Dropzone.UPLOADING
|
|
|
|
# Files that are either queued or uploading
|
|
getActiveFiles: -> file for file in @files when file.status == Dropzone.UPLOADING or file.status == Dropzone.QUEUED
|
|
|
|
|
|
init: ->
|
|
# In case it isn't set already
|
|
@element.setAttribute("enctype", "multipart/form-data") if @element.tagName == "form"
|
|
|
|
if @element.classList.contains("dropzone") and !@element.querySelector(".dz-message")
|
|
@element.appendChild Dropzone.createElement """<div class="dz-default dz-message"><span>#{@options.dictDefaultMessage}</span></div>"""
|
|
|
|
if @clickableElements.length
|
|
setupHiddenFileInput = =>
|
|
document.body.removeChild @hiddenFileInput if @hiddenFileInput
|
|
@hiddenFileInput = document.createElement "input"
|
|
@hiddenFileInput.setAttribute "type", "file"
|
|
@hiddenFileInput.setAttribute "multiple", "multiple" if !@options.maxFiles? || @options.maxFiles > 1
|
|
@hiddenFileInput.className = "dz-hidden-input"
|
|
|
|
@hiddenFileInput.setAttribute "accept", @options.acceptedFiles if @options.acceptedFiles?
|
|
|
|
# Not setting `display="none"` because some browsers don't accept clicks
|
|
# on elements that aren't displayed.
|
|
@hiddenFileInput.style.visibility = "hidden"
|
|
@hiddenFileInput.style.position = "absolute"
|
|
@hiddenFileInput.style.top = "0"
|
|
@hiddenFileInput.style.left = "0"
|
|
@hiddenFileInput.style.height = "0"
|
|
@hiddenFileInput.style.width = "0"
|
|
document.body.appendChild @hiddenFileInput
|
|
@hiddenFileInput.addEventListener "change", =>
|
|
files = @hiddenFileInput.files
|
|
@addFile file for file in files if files.length
|
|
setupHiddenFileInput()
|
|
setupHiddenFileInput()
|
|
|
|
@URL = window.URL ? window.webkitURL
|
|
|
|
|
|
# Setup all event listeners on the Dropzone object itself.
|
|
# They're not in @setupEventListeners() because they shouldn't be removed
|
|
# again when the dropzone gets disabled.
|
|
@on eventName, @options[eventName] for eventName in @events
|
|
|
|
@on "uploadprogress", => @updateTotalUploadProgress()
|
|
|
|
@on "removedfile", => @updateTotalUploadProgress()
|
|
|
|
@on "canceled", (file) => @emit "complete", file
|
|
|
|
# Emit a `queuecomplete` event if all files finished uploading.
|
|
@on "complete", (file) =>
|
|
if @getUploadingFiles().length == 0 and @getQueuedFiles().length == 0
|
|
# This needs to be deferred so that `queuecomplete` really triggers after `complete`
|
|
setTimeout (=> @emit "queuecomplete"), 0
|
|
|
|
|
|
noPropagation = (e) ->
|
|
e.stopPropagation()
|
|
if e.preventDefault
|
|
e.preventDefault()
|
|
else
|
|
e.returnValue = false
|
|
|
|
# Create the listeners
|
|
@listeners = [
|
|
{
|
|
element: @element
|
|
events:
|
|
"dragstart": (e) =>
|
|
@emit "dragstart", e
|
|
"dragenter": (e) =>
|
|
noPropagation e
|
|
@emit "dragenter", e
|
|
"dragover": (e) =>
|
|
# Makes it possible to drag files from chrome's download bar
|
|
# http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar
|
|
# Try is required to prevent bug in Internet Explorer 11 (SCRIPT65535 exception)
|
|
try efct = e.dataTransfer.effectAllowed
|
|
e.dataTransfer.dropEffect = if 'move' == efct or 'linkMove' == efct then 'move' else 'copy'
|
|
|
|
noPropagation e
|
|
@emit "dragover", e
|
|
"dragleave": (e) =>
|
|
@emit "dragleave", e
|
|
"drop": (e) =>
|
|
noPropagation e
|
|
@drop e
|
|
"dragend": (e) =>
|
|
@emit "dragend", e
|
|
|
|
# This is disabled right now, because the browsers don't implement it properly.
|
|
# "paste": (e) =>
|
|
# noPropagation e
|
|
# @paste e
|
|
}
|
|
]
|
|
|
|
@clickableElements.forEach (clickableElement) =>
|
|
@listeners.push
|
|
element: clickableElement
|
|
events:
|
|
"click": (evt) =>
|
|
# Only the actual dropzone or the message element should trigger file selection
|
|
if (clickableElement != @element) or (evt.target == @element or Dropzone.elementInside evt.target, @element.querySelector ".dz-message")
|
|
@hiddenFileInput.click() # Forward the click
|
|
|
|
|
|
@enable()
|
|
|
|
@options.init.call @
|
|
|
|
# Not fully tested yet
|
|
destroy: ->
|
|
@disable()
|
|
@removeAllFiles true
|
|
if @hiddenFileInput?.parentNode
|
|
@hiddenFileInput.parentNode.removeChild @hiddenFileInput
|
|
@hiddenFileInput = null
|
|
delete @element.dropzone
|
|
Dropzone.instances.splice Dropzone.instances.indexOf(this), 1
|
|
|
|
|
|
updateTotalUploadProgress: ->
|
|
totalBytesSent = 0
|
|
totalBytes = 0
|
|
|
|
activeFiles = @getActiveFiles()
|
|
|
|
if activeFiles.length
|
|
for file in @getActiveFiles()
|
|
totalBytesSent += file.upload.bytesSent
|
|
totalBytes += file.upload.total
|
|
totalUploadProgress = 100 * totalBytesSent / totalBytes
|
|
else
|
|
totalUploadProgress = 100
|
|
|
|
@emit "totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent
|
|
|
|
# @options.paramName can be a function taking one parameter rather than a string.
|
|
# A parameter name for a file is obtained simply by calling this with an index number.
|
|
_getParamName: (n) ->
|
|
if typeof @options.paramName is "function"
|
|
@options.paramName n
|
|
else
|
|
"#{@options.paramName}#{if @options.uploadMultiple then "[#{n}]" else ""}"
|
|
|
|
# Returns a form that can be used as fallback if the browser does not support DragnDrop
|
|
#
|
|
# If the dropzone is already a form, only the input field and button are returned. Otherwise a complete form element is provided.
|
|
# This code has to pass in IE7 :(
|
|
getFallbackForm: ->
|
|
return existingFallback if existingFallback = @getExistingFallback()
|
|
|
|
fieldsString = """<div class="dz-fallback">"""
|
|
fieldsString += """<p>#{@options.dictFallbackText}</p>""" if @options.dictFallbackText
|
|
fieldsString += """<input type="file" name="#{@_getParamName 0}" #{if @options.uploadMultiple then 'multiple="multiple"' } /><input type="submit" value="Upload!"></div>"""
|
|
|
|
fields = Dropzone.createElement fieldsString
|
|
if @element.tagName isnt "FORM"
|
|
form = Dropzone.createElement("""<form action="#{@options.url}" enctype="multipart/form-data" method="#{@options.method}"></form>""")
|
|
form.appendChild fields
|
|
else
|
|
# Make sure that the enctype and method attributes are set properly
|
|
@element.setAttribute "enctype", "multipart/form-data"
|
|
@element.setAttribute "method", @options.method
|
|
form ? fields
|
|
|
|
|
|
# Returns the fallback elements if they exist already
|
|
#
|
|
# This code has to pass in IE7 :(
|
|
getExistingFallback: ->
|
|
getFallback = (elements) -> return el for el in elements when /(^| )fallback($| )/.test el.className
|
|
|
|
for tagName in [ "div", "form" ]
|
|
return fallback if fallback = getFallback @element.getElementsByTagName tagName
|
|
|
|
|
|
# Activates all listeners stored in @listeners
|
|
setupEventListeners: ->
|
|
for elementListeners in @listeners
|
|
elementListeners.element.addEventListener event, listener, false for event, listener of elementListeners.events
|
|
|
|
|
|
# Deactivates all listeners stored in @listeners
|
|
removeEventListeners: ->
|
|
for elementListeners in @listeners
|
|
elementListeners.element.removeEventListener event, listener, false for event, listener of elementListeners.events
|
|
|
|
# Removes all event listeners and cancels all files in the queue or being processed.
|
|
disable: ->
|
|
@clickableElements.forEach (element) -> element.classList.remove "dz-clickable"
|
|
@removeEventListeners()
|
|
|
|
@cancelUpload file for file in @files
|
|
|
|
enable: ->
|
|
@clickableElements.forEach (element) -> element.classList.add "dz-clickable"
|
|
@setupEventListeners()
|
|
|
|
# Returns a nicely formatted filesize
|
|
filesize: (size) ->
|
|
if size >= 1024 * 1024 * 1024 * 1024 / 10
|
|
size = size / (1024 * 1024 * 1024 * 1024 / 10)
|
|
string = "TiB"
|
|
else if size >= 1024 * 1024 * 1024 / 10
|
|
size = size / (1024 * 1024 * 1024 / 10)
|
|
string = "GiB"
|
|
else if size >= 1024 * 1024 / 10
|
|
size = size / (1024 * 1024 / 10)
|
|
string = "MiB"
|
|
else if size >= 1024 / 10
|
|
size = size / (1024 / 10)
|
|
string = "KiB"
|
|
else
|
|
size = size * 10
|
|
string = "b"
|
|
"<strong>#{Math.round(size)/10}</strong> #{string}"
|
|
|
|
|
|
# Adds or removes the `dz-max-files-reached` class from the form.
|
|
_updateMaxFilesReachedClass: ->
|
|
if @options.maxFiles? and @getAcceptedFiles().length >= @options.maxFiles
|
|
@emit 'maxfilesreached', @files if @getAcceptedFiles().length == @options.maxFiles
|
|
@element.classList.add "dz-max-files-reached"
|
|
else
|
|
@element.classList.remove "dz-max-files-reached"
|
|
|
|
|
|
|
|
drop: (e) ->
|
|
return unless e.dataTransfer
|
|
@emit "drop", e
|
|
|
|
files = e.dataTransfer.files
|
|
|
|
# Even if it's a folder, files.length will contain the folders.
|
|
if files.length
|
|
items = e.dataTransfer.items
|
|
if items and items.length and (items[0].webkitGetAsEntry?)
|
|
# The browser supports dropping of folders, so handle items instead of files
|
|
@_addFilesFromItems items
|
|
else
|
|
@handleFiles files
|
|
return
|
|
|
|
paste: (e) ->
|
|
return unless e?.clipboardData?.items?
|
|
|
|
@emit "paste", e
|
|
items = e.clipboardData.items
|
|
|
|
@_addFilesFromItems items if items.length
|
|
|
|
|
|
handleFiles: (files) ->
|
|
@addFile file for file in files
|
|
|
|
# When a folder is dropped (or files are pasted), items must be handled
|
|
# instead of files.
|
|
_addFilesFromItems: (items) ->
|
|
for item in items
|
|
if item.webkitGetAsEntry? and entry = item.webkitGetAsEntry()
|
|
if entry.isFile
|
|
@addFile item.getAsFile()
|
|
else if entry.isDirectory
|
|
# Append all files from that directory to files
|
|
@_addFilesFromDirectory entry, entry.name
|
|
else if item.getAsFile?
|
|
if !item.kind? or item.kind == "file"
|
|
@addFile item.getAsFile()
|
|
|
|
|
|
# Goes through the directory, and adds each file it finds recursively
|
|
_addFilesFromDirectory: (directory, path) ->
|
|
dirReader = directory.createReader()
|
|
|
|
entriesReader = (entries) =>
|
|
for entry in entries
|
|
if entry.isFile
|
|
entry.file (file) =>
|
|
return if @options.ignoreHiddenFiles and file.name.substring(0, 1) is '.'
|
|
file.fullPath = "#{path}/#{file.name}"
|
|
@addFile file
|
|
else if entry.isDirectory
|
|
@_addFilesFromDirectory entry, "#{path}/#{entry.name}"
|
|
return
|
|
|
|
dirReader.readEntries entriesReader, (error) -> console?.log? error
|
|
|
|
|
|
|
|
# If `done()` is called without argument the file is accepted
|
|
# If you call it with an error message, the file is rejected
|
|
# (This allows for asynchronous validation)
|
|
#
|
|
# This function checks the filesize, and if the file.type passes the
|
|
# `acceptedFiles` check.
|
|
accept: (file, done) ->
|
|
if file.size > @options.maxFilesize * 1024 * 1024
|
|
done @options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", @options.maxFilesize)
|
|
else unless Dropzone.isValidFile file, @options.acceptedFiles
|
|
done @options.dictInvalidFileType
|
|
else if @options.maxFiles? and @getAcceptedFiles().length >= @options.maxFiles
|
|
done @options.dictMaxFilesExceeded.replace "{{maxFiles}}", @options.maxFiles
|
|
@emit "maxfilesexceeded", file
|
|
else
|
|
@options.accept.call this, file, done
|
|
|
|
addFile: (file) ->
|
|
file.upload =
|
|
progress: 0
|
|
# Setting the total upload size to file.size for the beginning
|
|
# It's actual different than the size to be transmitted.
|
|
total: file.size
|
|
bytesSent: 0
|
|
@files.push file
|
|
|
|
file.status = Dropzone.ADDED
|
|
|
|
@emit "addedfile", file
|
|
|
|
@_enqueueThumbnail file
|
|
|
|
@accept file, (error) =>
|
|
if error
|
|
file.accepted = false
|
|
@_errorProcessing [ file ], error # Will set the file.status
|
|
else
|
|
file.accepted = true
|
|
@enqueueFile file if @options.autoQueue # Will set .accepted = true
|
|
@_updateMaxFilesReachedClass()
|
|
|
|
|
|
# Wrapper for enqueueFile
|
|
enqueueFiles: (files) -> @enqueueFile file for file in files; null
|
|
|
|
enqueueFile: (file) ->
|
|
if file.status == Dropzone.ADDED and file.accepted == true
|
|
file.status = Dropzone.QUEUED
|
|
if @options.autoProcessQueue
|
|
setTimeout (=> @processQueue()), 0 # Deferring the call
|
|
else
|
|
throw new Error "This file can't be queued because it has already been processed or was rejected."
|
|
|
|
|
|
_thumbnailQueue: [ ]
|
|
_processingThumbnail: no
|
|
_enqueueThumbnail: (file) ->
|
|
if @options.createImageThumbnails and file.type.match(/image.*/) and file.size <= @options.maxThumbnailFilesize * 1024 * 1024
|
|
@_thumbnailQueue.push(file)
|
|
setTimeout (=> @_processThumbnailQueue()), 0 # Deferring the call
|
|
|
|
_processThumbnailQueue: ->
|
|
return if @_processingThumbnail or @_thumbnailQueue.length == 0
|
|
|
|
@_processingThumbnail = yes
|
|
@createThumbnail @_thumbnailQueue.shift(), =>
|
|
@_processingThumbnail = no
|
|
@_processThumbnailQueue()
|
|
|
|
|
|
# Can be called by the user to remove a file
|
|
removeFile: (file) ->
|
|
@cancelUpload file if file.status == Dropzone.UPLOADING
|
|
@files = without @files, file
|
|
|
|
@emit "removedfile", file
|
|
@emit "reset" if @files.length == 0
|
|
|
|
# Removes all files that aren't currently processed from the list
|
|
removeAllFiles: (cancelIfNecessary = off) ->
|
|
# Create a copy of files since removeFile() changes the @files array.
|
|
for file in @files.slice()
|
|
@removeFile file if file.status != Dropzone.UPLOADING || cancelIfNecessary
|
|
return null
|
|
|
|
createThumbnail: (file, callback) ->
|
|
|
|
fileReader = new FileReader
|
|
|
|
fileReader.onload = =>
|
|
# Not using `new Image` here because of a bug in latest Chrome versions.
|
|
# See https://github.com/enyo/dropzone/pull/226
|
|
img = document.createElement "img"
|
|
|
|
img.onload = =>
|
|
file.width = img.width
|
|
file.height = img.height
|
|
|
|
resizeInfo = @options.resize.call @, file
|
|
|
|
resizeInfo.trgWidth ?= resizeInfo.optWidth
|
|
resizeInfo.trgHeight ?= resizeInfo.optHeight
|
|
|
|
canvas = document.createElement "canvas"
|
|
ctx = canvas.getContext "2d"
|
|
canvas.width = resizeInfo.trgWidth
|
|
canvas.height = resizeInfo.trgHeight
|
|
|
|
# This is a bugfix for iOS' scaling bug.
|
|
drawImageIOSFix ctx, img, resizeInfo.srcX ? 0, resizeInfo.srcY ? 0, resizeInfo.srcWidth, resizeInfo.srcHeight, resizeInfo.trgX ? 0, resizeInfo.trgY ? 0, resizeInfo.trgWidth, resizeInfo.trgHeight
|
|
|
|
thumbnail = canvas.toDataURL "image/png"
|
|
|
|
@emit "thumbnail", file, thumbnail
|
|
callback() if callback?
|
|
|
|
img.src = fileReader.result
|
|
|
|
fileReader.readAsDataURL file
|
|
|
|
|
|
# Goes through the queue and processes files if there aren't too many already.
|
|
processQueue: ->
|
|
parallelUploads = @options.parallelUploads
|
|
processingLength = @getUploadingFiles().length
|
|
i = processingLength
|
|
|
|
# There are already at least as many files uploading than should be
|
|
return if processingLength >= parallelUploads
|
|
|
|
queuedFiles = @getQueuedFiles()
|
|
|
|
return unless queuedFiles.length > 0
|
|
|
|
if @options.uploadMultiple
|
|
# The files should be uploaded in one request
|
|
@processFiles queuedFiles.slice 0, (parallelUploads - processingLength)
|
|
else
|
|
while i < parallelUploads
|
|
return unless queuedFiles.length # Nothing left to process
|
|
@processFile queuedFiles.shift()
|
|
i++
|
|
|
|
|
|
# Wrapper for `processFiles`
|
|
processFile: (file) -> @processFiles [ file ]
|
|
|
|
|
|
# Loads the file, then calls finishedLoading()
|
|
processFiles: (files) ->
|
|
for file in files
|
|
file.processing = yes # Backwards compatibility
|
|
file.status = Dropzone.UPLOADING
|
|
|
|
@emit "processing", file
|
|
|
|
@emit "processingmultiple", files if @options.uploadMultiple
|
|
|
|
@uploadFiles files
|
|
|
|
|
|
|
|
_getFilesWithXhr: (xhr) -> files = (file for file in @files when file.xhr == xhr)
|
|
|
|
|
|
# Cancels the file upload and sets the status to CANCELED
|
|
# **if** the file is actually being uploaded.
|
|
# If it's still in the queue, the file is being removed from it and the status
|
|
# set to CANCELED.
|
|
cancelUpload: (file) ->
|
|
if file.status == Dropzone.UPLOADING
|
|
groupedFiles = @_getFilesWithXhr file.xhr
|
|
groupedFile.status = Dropzone.CANCELED for groupedFile in groupedFiles
|
|
file.xhr.abort()
|
|
@emit "canceled", groupedFile for groupedFile in groupedFiles
|
|
@emit "canceledmultiple", groupedFiles if @options.uploadMultiple
|
|
|
|
else if file.status in [ Dropzone.ADDED, Dropzone.QUEUED ]
|
|
file.status = Dropzone.CANCELED
|
|
@emit "canceled", file
|
|
@emit "canceledmultiple", [ file ] if @options.uploadMultiple
|
|
|
|
@processQueue() if @options.autoProcessQueue
|
|
|
|
# Wrapper for uploadFiles()
|
|
uploadFile: (file) -> @uploadFiles [ file ]
|
|
|
|
uploadFiles: (files) ->
|
|
xhr = new XMLHttpRequest()
|
|
|
|
# Put the xhr object in the file objects to be able to reference it later.
|
|
file.xhr = xhr for file in files
|
|
|
|
xhr.open @options.method, @options.url, true
|
|
|
|
# Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
|
|
xhr.withCredentials = !!@options.withCredentials
|
|
|
|
|
|
response = null
|
|
|
|
handleError = =>
|
|
for file in files
|
|
@_errorProcessing files, response || @options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr
|
|
|
|
|
|
updateProgress = (e) =>
|
|
if e?
|
|
progress = 100 * e.loaded / e.total
|
|
|
|
for file in files
|
|
file.upload =
|
|
progress: progress
|
|
total: e.total
|
|
bytesSent: e.loaded
|
|
else
|
|
# Called when the file finished uploading
|
|
|
|
allFilesFinished = yes
|
|
|
|
progress = 100
|
|
|
|
for file in files
|
|
allFilesFinished = no unless file.upload.progress == 100 and file.upload.bytesSent == file.upload.total
|
|
file.upload.progress = progress
|
|
file.upload.bytesSent = file.upload.total
|
|
|
|
# Nothing to do, all files already at 100%
|
|
return if allFilesFinished
|
|
|
|
for file in files
|
|
@emit "uploadprogress", file, progress, file.upload.bytesSent
|
|
|
|
xhr.onload = (e) =>
|
|
return if files[0].status == Dropzone.CANCELED
|
|
|
|
return unless xhr.readyState is 4
|
|
|
|
response = xhr.responseText
|
|
|
|
if xhr.getResponseHeader("content-type") and ~xhr.getResponseHeader("content-type").indexOf "application/json"
|
|
try
|
|
response = JSON.parse response
|
|
catch e
|
|
response = "Invalid JSON response from server."
|
|
|
|
updateProgress()
|
|
|
|
unless 200 <= xhr.status < 300
|
|
handleError()
|
|
else
|
|
@_finished files, response, e
|
|
|
|
xhr.onerror = =>
|
|
return if files[0].status == Dropzone.CANCELED
|
|
handleError()
|
|
|
|
# Some browsers do not have the .upload property
|
|
progressObj = xhr.upload ? xhr
|
|
progressObj.onprogress = updateProgress
|
|
|
|
headers =
|
|
"Accept": "application/json",
|
|
"Cache-Control": "no-cache",
|
|
"X-Requested-With": "XMLHttpRequest",
|
|
|
|
extend headers, @options.headers if @options.headers
|
|
|
|
xhr.setRequestHeader headerName, headerValue for headerName, headerValue of headers
|
|
|
|
formData = new FormData()
|
|
|
|
# Adding all @options parameters
|
|
formData.append key, value for key, value of @options.params if @options.params
|
|
|
|
# Let the user add additional data if necessary
|
|
@emit "sending", file, xhr, formData for file in files
|
|
@emit "sendingmultiple", files, xhr, formData if @options.uploadMultiple
|
|
|
|
|
|
# Take care of other input elements
|
|
if @element.tagName == "FORM"
|
|
for input in @element.querySelectorAll "input, textarea, select, button"
|
|
inputName = input.getAttribute "name"
|
|
inputType = input.getAttribute "type"
|
|
|
|
if input.tagName == "SELECT" and input.hasAttribute "multiple"
|
|
# Possibly multiple values
|
|
formData.append inputName, option.value for option in input.options when option.selected
|
|
else if !inputType or (inputType.toLowerCase() not in [ "checkbox", "radio" ]) or input.checked
|
|
formData.append inputName, input.value
|
|
|
|
|
|
# Finally add the file
|
|
# Has to be last because some servers (eg: S3) expect the file to be the
|
|
# last parameter
|
|
formData.append @_getParamName(i), files[i], files[i].name for i in [0..files.length-1]
|
|
|
|
xhr.send formData
|
|
|
|
|
|
# Called internally when processing is finished.
|
|
# Individual callbacks have to be called in the appropriate sections.
|
|
_finished: (files, responseText, e) ->
|
|
for file in files
|
|
file.status = Dropzone.SUCCESS
|
|
@emit "success", file, responseText, e
|
|
@emit "complete", file
|
|
if @options.uploadMultiple
|
|
@emit "successmultiple", files, responseText, e
|
|
@emit "completemultiple", files
|
|
|
|
@processQueue() if @options.autoProcessQueue
|
|
|
|
# Called internally when processing is finished.
|
|
# Individual callbacks have to be called in the appropriate sections.
|
|
_errorProcessing: (files, message, xhr) ->
|
|
for file in files
|
|
file.status = Dropzone.ERROR
|
|
@emit "error", file, message, xhr
|
|
@emit "complete", file
|
|
if @options.uploadMultiple
|
|
@emit "errormultiple", files, message, xhr
|
|
@emit "completemultiple", files
|
|
|
|
@processQueue() if @options.autoProcessQueue
|
|
|
|
|
|
|
|
Dropzone.version = "3.10.2"
|
|
|
|
|
|
# This is a map of options for your different dropzones. Add configurations
|
|
# to this object for your different dropzone elemens.
|
|
#
|
|
# Example:
|
|
#
|
|
# Dropzone.options.myDropzoneElementId = { maxFilesize: 1 };
|
|
#
|
|
# To disable autoDiscover for a specific element, you can set `false` as an option:
|
|
#
|
|
# Dropzone.options.myDisabledElementId = false;
|
|
#
|
|
# And in html:
|
|
#
|
|
# <form action="/upload" id="my-dropzone-element-id" class="dropzone"></form>
|
|
Dropzone.options = { }
|
|
|
|
|
|
# Returns the options for an element or undefined if none available.
|
|
Dropzone.optionsForElement = (element) ->
|
|
# Get the `Dropzone.options.elementId` for this element if it exists
|
|
if element.getAttribute("id") then Dropzone.options[camelize element.getAttribute "id"] else undefined
|
|
|
|
|
|
# Holds a list of all dropzone instances
|
|
Dropzone.instances = [ ]
|
|
|
|
# Returns the dropzone for given element if any
|
|
Dropzone.forElement = (element) ->
|
|
element = document.querySelector element if typeof element == "string"
|
|
throw new Error "No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone." unless element?.dropzone?
|
|
return element.dropzone
|
|
|
|
|
|
# Set to false if you don't want Dropzone to automatically find and attach to .dropzone elements.
|
|
Dropzone.autoDiscover = on
|
|
|
|
# Looks for all .dropzone elements and creates a dropzone for them
|
|
Dropzone.discover = ->
|
|
if document.querySelectorAll
|
|
dropzones = document.querySelectorAll ".dropzone"
|
|
else
|
|
dropzones = [ ]
|
|
# IE :(
|
|
checkElements = (elements) ->
|
|
for el in elements
|
|
dropzones.push el if /(^| )dropzone($| )/.test el.className
|
|
checkElements document.getElementsByTagName "div"
|
|
checkElements document.getElementsByTagName "form"
|
|
|
|
for dropzone in dropzones
|
|
# Create a dropzone unless auto discover has been disabled for specific element
|
|
new Dropzone dropzone unless Dropzone.optionsForElement(dropzone) == false
|
|
|
|
|
|
|
|
# Since the whole Drag'n'Drop API is pretty new, some browsers implement it,
|
|
# but not correctly.
|
|
# So I created a blacklist of userAgents. Yes, yes. Browser sniffing, I know.
|
|
# But what to do when browsers *theoretically* support an API, but crash
|
|
# when using it.
|
|
#
|
|
# This is a list of regular expressions tested against navigator.userAgent
|
|
#
|
|
# ** It should only be used on browser that *do* support the API, but
|
|
# incorrectly **
|
|
#
|
|
Dropzone.blacklistedBrowsers = [
|
|
# The mac os version of opera 12 seems to have a problem with the File drag'n'drop API.
|
|
/opera.*Macintosh.*version\/12/i
|
|
# /MSIE\ 10/i
|
|
]
|
|
|
|
|
|
# Checks if the browser is supported
|
|
Dropzone.isBrowserSupported = ->
|
|
capableBrowser = yes
|
|
|
|
if window.File and window.FileReader and window.FileList and window.Blob and window.FormData and document.querySelector
|
|
unless "classList" of document.createElement "a"
|
|
capableBrowser = no
|
|
else
|
|
# The browser supports the API, but may be blacklisted.
|
|
for regex in Dropzone.blacklistedBrowsers
|
|
if regex.test navigator.userAgent
|
|
capableBrowser = no
|
|
continue
|
|
else
|
|
capableBrowser = no
|
|
|
|
capableBrowser
|
|
|
|
|
|
|
|
|
|
# Returns an array without the rejected item
|
|
without = (list, rejectedItem) -> item for item in list when item isnt rejectedItem
|
|
|
|
# abc-def_ghi -> abcDefGhi
|
|
camelize = (str) -> str.replace /[\-_](\w)/g, (match) -> match.charAt(1).toUpperCase()
|
|
|
|
# Creates an element from string
|
|
Dropzone.createElement = (string) ->
|
|
div = document.createElement "div"
|
|
div.innerHTML = string
|
|
div.childNodes[0]
|
|
|
|
# Tests if given element is inside (or simply is) the container
|
|
Dropzone.elementInside = (element, container) ->
|
|
return yes if element == container # Coffeescript doesn't support do/while loops
|
|
return yes while element = element.parentNode when element == container
|
|
return no
|
|
|
|
|
|
|
|
Dropzone.getElement = (el, name) ->
|
|
if typeof el == "string"
|
|
element = document.querySelector el
|
|
else if el.nodeType?
|
|
element = el
|
|
throw new Error "Invalid `#{name}` option provided. Please provide a CSS selector or a plain HTML element." unless element?
|
|
return element
|
|
|
|
|
|
Dropzone.getElements = (els, name) ->
|
|
if els instanceof Array
|
|
elements = [ ]
|
|
try
|
|
elements.push @getElement el, name for el in els
|
|
catch e
|
|
elements = null
|
|
else if typeof els == "string"
|
|
elements = [ ]
|
|
elements.push el for el in document.querySelectorAll els
|
|
else if els.nodeType?
|
|
elements = [ els ]
|
|
|
|
throw new Error "Invalid `#{name}` option provided. Please provide a CSS selector, a plain HTML element or a list of those." unless elements? and elements.length
|
|
|
|
return elements
|
|
|
|
# Asks the user the question and calls accepted or rejected accordingly
|
|
#
|
|
# The default implementation just uses `window.confirm` and then calls the
|
|
# appropriate callback.
|
|
Dropzone.confirm = (question, accepted, rejected) ->
|
|
if window.confirm question
|
|
accepted()
|
|
else if rejected?
|
|
rejected()
|
|
|
|
# Validates the mime type like this:
|
|
#
|
|
# https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept
|
|
Dropzone.isValidFile = (file, acceptedFiles) ->
|
|
return yes unless acceptedFiles # If there are no accepted mime types, it's OK
|
|
acceptedFiles = acceptedFiles.split ","
|
|
|
|
mimeType = file.type
|
|
baseMimeType = mimeType.replace /\/.*$/, ""
|
|
|
|
for validType in acceptedFiles
|
|
validType = validType.trim()
|
|
if validType.charAt(0) == "."
|
|
return yes if file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) != -1
|
|
else if /\/\*$/.test validType
|
|
# This is something like a image/* mime type
|
|
return yes if baseMimeType == validType.replace /\/.*$/, ""
|
|
else
|
|
return yes if mimeType == validType
|
|
|
|
return no
|
|
|
|
|
|
# Augment jQuery
|
|
if jQuery?
|
|
jQuery.fn.dropzone = (options) ->
|
|
this.each -> new Dropzone this, options
|
|
|
|
|
|
|
|
|
|
if module?
|
|
module.exports = Dropzone
|
|
else
|
|
window.Dropzone = Dropzone
|
|
|
|
|
|
|
|
|
|
|
|
# Dropzone file status codes
|
|
Dropzone.ADDED = "added"
|
|
|
|
Dropzone.QUEUED = "queued"
|
|
# For backwards compatibility. Now, if a file is accepted, it's either queued
|
|
# or uploading.
|
|
Dropzone.ACCEPTED = Dropzone.QUEUED
|
|
|
|
Dropzone.UPLOADING = "uploading"
|
|
Dropzone.PROCESSING = Dropzone.UPLOADING # alias
|
|
|
|
Dropzone.CANCELED = "canceled"
|
|
Dropzone.ERROR = "error"
|
|
Dropzone.SUCCESS = "success"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
Bugfix for iOS 6 and 7
|
|
Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios
|
|
based on the work of https://github.com/stomita/ios-imagefile-megapixel
|
|
|
|
###
|
|
|
|
# Detecting vertical squash in loaded image.
|
|
# Fixes a bug which squash image vertically while drawing into canvas for some images.
|
|
# This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel
|
|
detectVerticalSquash = (img) ->
|
|
iw = img.naturalWidth
|
|
ih = img.naturalHeight
|
|
canvas = document.createElement("canvas")
|
|
canvas.width = 1
|
|
canvas.height = ih
|
|
ctx = canvas.getContext("2d")
|
|
ctx.drawImage img, 0, 0
|
|
data = ctx.getImageData(0, 0, 1, ih).data
|
|
|
|
|
|
# search image edge pixel position in case it is squashed vertically.
|
|
sy = 0
|
|
ey = ih
|
|
py = ih
|
|
while py > sy
|
|
alpha = data[(py - 1) * 4 + 3]
|
|
|
|
if alpha is 0 then ey = py else sy = py
|
|
|
|
py = (ey + sy) >> 1
|
|
ratio = (py / ih)
|
|
|
|
if (ratio is 0) then 1 else ratio
|
|
|
|
# A replacement for context.drawImage
|
|
# (args are for source and destination).
|
|
drawImageIOSFix = (ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) ->
|
|
vertSquashRatio = detectVerticalSquash img
|
|
ctx.drawImage img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
###
|
|
# contentloaded.js
|
|
#
|
|
# Author: Diego Perini (diego.perini at gmail.com)
|
|
# Summary: cross-browser wrapper for DOMContentLoaded
|
|
# Updated: 20101020
|
|
# License: MIT
|
|
# Version: 1.2
|
|
#
|
|
# URL:
|
|
# http://javascript.nwbox.com/ContentLoaded/
|
|
# http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
|
|
###
|
|
|
|
# @win window reference
|
|
# @fn function reference
|
|
contentLoaded = (win, fn) ->
|
|
done = false
|
|
top = true
|
|
doc = win.document
|
|
root = doc.documentElement
|
|
add = (if doc.addEventListener then "addEventListener" else "attachEvent")
|
|
rem = (if doc.addEventListener then "removeEventListener" else "detachEvent")
|
|
pre = (if doc.addEventListener then "" else "on")
|
|
init = (e) ->
|
|
return if e.type is "readystatechange" and doc.readyState isnt "complete"
|
|
((if e.type is "load" then win else doc))[rem] pre + e.type, init, false
|
|
fn.call win, e.type or e if not done and (done = true)
|
|
|
|
poll = ->
|
|
try
|
|
root.doScroll "left"
|
|
catch e
|
|
setTimeout poll, 50
|
|
return
|
|
init "poll"
|
|
|
|
unless doc.readyState is "complete"
|
|
if doc.createEventObject and root.doScroll
|
|
try
|
|
top = not win.frameElement
|
|
poll() if top
|
|
doc[add] pre + "DOMContentLoaded", init, false
|
|
doc[add] pre + "readystatechange", init, false
|
|
win[add] pre + "load", init, false
|
|
|
|
|
|
# As a single function to be able to write tests.
|
|
Dropzone._autoDiscoverFunction = -> Dropzone.discover() if Dropzone.autoDiscover
|
|
contentLoaded window, Dropzone._autoDiscoverFunction
|