Security update to Drupal 8.4.6
[yaffs-website] / vendor / enyo / dropzone / src / dropzone.coffee
1 ###
2 #
3 # More info at [www.dropzonejs.com](http://www.dropzonejs.com)
4 #
5 # Copyright (c) 2012, Matias Meno
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # THE SOFTWARE.
24 #
25 ###
26
27
28 noop = ->
29
30
31 # The Emitter class provides the ability to call `.on()` on Dropzone to listen
32 # to events.
33 # It is strongly based on component's emitter class, and I removed the
34 # functionality because of the dependency hell with different frameworks.
35 class Emitter
36
37   # Add an event listener for given event
38   addEventListener: @::on
39   on: (event, fn) ->
40     @_callbacks = @_callbacks || {}
41     # Create namespace for this event
42     @_callbacks[event] = [] unless @_callbacks[event]
43     @_callbacks[event].push fn
44     return @
45
46
47   emit: (event, args...) ->
48     @_callbacks = @_callbacks || {}
49     callbacks = @_callbacks[event]
50
51     if callbacks
52       callback.apply @, args for callback in callbacks
53
54     return @
55
56   # Remove event listener for given event. If fn is not provided, all event
57   # listeners for that event will be removed. If neither is provided, all
58   # event listeners will be removed.
59   removeListener: @::off
60   removeAllListeners: @::off
61   removeEventListener: @::off
62   off: (event, fn) ->
63     if !@_callbacks || arguments.length == 0
64       @_callbacks = {}
65       return @
66
67     # specific event
68     callbacks = @_callbacks[event]
69     return @ unless callbacks
70
71     # remove all handlers
72     if arguments.length == 1
73       delete @_callbacks[event]
74       return @
75
76     # remove specific handler
77     for callback, i in callbacks
78       if callback == fn
79         callbacks.splice i, 1
80         break
81
82     return @
83
84 class Dropzone extends Emitter
85
86   # Exposing the emitter class, mainly for tests
87   Emitter: Emitter
88
89   ###
90   This is a list of all available events you can register on a dropzone object.
91
92   You can register an event handler like this:
93
94       dropzone.on("dragEnter", function() { });
95
96   ###
97   events: [
98     "drop"
99     "dragstart"
100     "dragend"
101     "dragenter"
102     "dragover"
103     "dragleave"
104     "addedfile"
105     "addedfiles"
106     "removedfile"
107     "thumbnail"
108     "error"
109     "errormultiple"
110     "processing"
111     "processingmultiple"
112     "uploadprogress"
113     "totaluploadprogress"
114     "sending"
115     "sendingmultiple"
116     "success"
117     "successmultiple"
118     "canceled"
119     "canceledmultiple"
120     "complete"
121     "completemultiple"
122     "reset"
123     "maxfilesexceeded"
124     "maxfilesreached"
125     "queuecomplete"
126   ]
127
128
129
130   defaultOptions:
131     url: null
132     method: "post"
133     withCredentials: no
134     parallelUploads: 2
135     uploadMultiple: no # Whether to send multiple files in one request.
136     maxFilesize: 256 # in MB
137     paramName: "file" # The name of the file param that gets transferred.
138     createImageThumbnails: true
139     maxThumbnailFilesize: 10 # in MB. When the filename exceeds this limit, the thumbnail will not be generated.
140     thumbnailWidth: 120
141     thumbnailHeight: 120
142
143     # The base that is used to calculate the filesize. You can change this to
144     # 1024 if you would rather display kibibytes, mebibytes, etc...
145     # 1024 is technically incorrect,
146     # because `1024 bytes` are `1 kibibyte` not `1 kilobyte`.
147     # You can change this to `1024` if you don't care about validity.
148     filesizeBase: 1000
149
150     # Can be used to limit the maximum number of files that will be handled
151     # by this Dropzone
152     maxFiles: null
153
154     # Can be an object of additional parameters to transfer to the server.
155     # This is the same as adding hidden input fields in the form element.
156     params: { }
157
158     # If true, the dropzone will present a file selector when clicked.
159     clickable: yes
160
161     # Whether hidden files in directories should be ignored.
162     ignoreHiddenFiles: yes
163
164     # You can set accepted mime types here.
165     #
166     # The default implementation of the `accept()` function will check this
167     # property, and if the Dropzone is clickable this will be used as
168     # `accept` attribute.
169     #
170     # This is a comma separated list of mime types or extensions. E.g.:
171     #
172     #     audio/*,video/*,image/png,.pdf
173     #
174     # See https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept
175     # for a reference.
176     acceptedFiles: null
177
178     # @deprecated
179     # Use acceptedFiles instead.
180     acceptedMimeTypes: null
181
182     # If false, files will be added to the queue but the queue will not be
183     # processed automatically.
184     # This can be useful if you need some additional user input before sending
185     # files (or if you want want all files sent at once).
186     # If you're ready to send the file simply call myDropzone.processQueue()
187     autoProcessQueue: on
188
189     # If false, files added to the dropzone will not be queued by default.
190     # You'll have to call `enqueueFile(file)` manually.
191     autoQueue: on
192
193     # If true, Dropzone will add a link to each file preview to cancel/remove
194     # the upload.
195     # See dictCancelUpload and dictRemoveFile to use different words.
196     addRemoveLinks: no
197
198     # A CSS selector or HTML element for the file previews container.
199     # If null, the dropzone element itself will be used.
200     # If false, previews won't be rendered.
201     previewsContainer: null
202
203     # Selector for hidden input container
204     hiddenInputContainer: "body"
205
206     # If null, no capture type will be specified
207     # If camera, mobile devices will skip the file selection and choose camera
208     # If microphone, mobile devices will skip the file selection and choose the microphone
209     # If camcorder, mobile devices will skip the file selection and choose the camera in video mode
210     # On apple devices multiple must be set to false.  AcceptedFiles may need to
211     # be set to an appropriate mime type (e.g. "image/*", "audio/*", or "video/*").
212     capture: null
213
214     # Before the file is appended to the formData, the function _renameFilename is performed for file.name
215     # which executes the function defined in renameFilename
216     renameFilename: null
217
218     # Dictionary
219
220     # The text used before any files are dropped
221     dictDefaultMessage: "Drop files here to upload"
222
223     # The text that replaces the default message text it the browser is not supported
224     dictFallbackMessage: "Your browser does not support drag'n'drop file uploads."
225
226     # The text that will be added before the fallback form
227     # If null, no text will be added at all.
228     dictFallbackText: "Please use the fallback form below to upload your files like in the olden days."
229
230     # If the filesize is too big.
231     dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB."
232
233     # If the file doesn't match the file type.
234     dictInvalidFileType: "You can't upload files of this type."
235
236     # If the server response was invalid.
237     dictResponseError: "Server responded with {{statusCode}} code."
238
239     # If used, the text to be used for the cancel upload link.
240     dictCancelUpload: "Cancel upload"
241
242     # If used, the text to be used for confirmation when cancelling upload.
243     dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?"
244
245     # If used, the text to be used to remove a file.
246     dictRemoveFile: "Remove file"
247
248     # If this is not null, then the user will be prompted before removing a file.
249     dictRemoveFileConfirmation: null
250
251     # Displayed when the maxFiles have been exceeded
252     # You can use {{maxFiles}} here, which will be replaced by the option.
253     dictMaxFilesExceeded: "You can not upload any more files."
254
255
256     # If `done()` is called without argument the file is accepted
257     # If you call it with an error message, the file is rejected
258     # (This allows for asynchronous validation).
259     accept: (file, done) -> done()
260
261
262     # Called when dropzone initialized
263     # You can add event listeners here
264     init: -> noop
265
266     # Used to debug dropzone and force the fallback form.
267     forceFallback: off
268
269     # Called when the browser does not support drag and drop
270     fallback: ->
271       # This code should pass in IE7... :(
272       @element.className = "#{@element.className} dz-browser-not-supported"
273
274       for child in @element.getElementsByTagName "div"
275         if /(^| )dz-message($| )/.test child.className
276           messageElement = child
277           child.className = "dz-message" # Removes the 'dz-default' class
278           continue
279       unless messageElement
280         messageElement = Dropzone.createElement """<div class="dz-message"><span></span></div>"""
281         @element.appendChild messageElement
282
283       span = messageElement.getElementsByTagName("span")[0]
284       if span
285         if span.textContent?
286           span.textContent = @options.dictFallbackMessage
287         else if span.innerText?
288           span.innerText = @options.dictFallbackMessage
289
290       @element.appendChild @getFallbackForm()
291
292
293
294     # Gets called to calculate the thumbnail dimensions.
295     #
296     # You can use file.width, file.height, options.thumbnailWidth and
297     # options.thumbnailHeight to calculate the dimensions.
298     #
299     # The dimensions are going to be used like this:
300     #
301     #     var info = @options.resize.call(this, file);
302     #     ctx.drawImage(img, info.srcX, info.srcY, info.srcWidth, info.srcHeight, info.trgX, info.trgY, info.trgWidth, info.trgHeight);
303     #
304     #  srcX, srcy, trgX and trgY can be omitted (in which case 0 is assumed).
305     #  trgWidth and trgHeight can be omitted (in which case the options.thumbnailWidth / options.thumbnailHeight are used)
306     resize: (file) ->
307       info =
308         srcX: 0
309         srcY: 0
310         srcWidth: file.width
311         srcHeight: file.height
312
313       srcRatio = file.width / file.height
314
315       info.optWidth = @options.thumbnailWidth
316       info.optHeight = @options.thumbnailHeight
317
318       # automatically calculate dimensions if not specified
319       if !info.optWidth? and !info.optHeight?
320         info.optWidth = info.srcWidth
321         info.optHeight = info.srcHeight
322       else if !info.optWidth?
323         info.optWidth = srcRatio * info.optHeight
324       else if !info.optHeight?
325         info.optHeight = (1/srcRatio) * info.optWidth
326
327       trgRatio = info.optWidth / info.optHeight
328
329       if file.height < info.optHeight or file.width < info.optWidth
330         # This image is smaller than the canvas
331         info.trgHeight = info.srcHeight
332         info.trgWidth = info.srcWidth
333
334       else
335         # Image is bigger and needs rescaling
336         if srcRatio > trgRatio
337           info.srcHeight = file.height
338           info.srcWidth = info.srcHeight * trgRatio
339         else
340           info.srcWidth = file.width
341           info.srcHeight = info.srcWidth / trgRatio
342
343       info.srcX = (file.width - info.srcWidth) / 2
344       info.srcY = (file.height - info.srcHeight) / 2
345
346       return info
347
348
349     ###
350     Those functions register themselves to the events on init and handle all
351     the user interface specific stuff. Overwriting them won't break the upload
352     but can break the way it's displayed.
353     You can overwrite them if you don't like the default behavior. If you just
354     want to add an additional event handler, register it on the dropzone object
355     and don't overwrite those options.
356     ###
357
358
359
360
361     # Those are self explanatory and simply concern the DragnDrop.
362     drop: (e) -> @element.classList.remove "dz-drag-hover"
363     dragstart: noop
364     dragend: (e) -> @element.classList.remove "dz-drag-hover"
365     dragenter: (e) -> @element.classList.add "dz-drag-hover"
366     dragover: (e) -> @element.classList.add "dz-drag-hover"
367     dragleave: (e) -> @element.classList.remove "dz-drag-hover"
368
369     paste: noop
370
371     # Called whenever there are no files left in the dropzone anymore, and the
372     # dropzone should be displayed as if in the initial state.
373     reset: ->
374       @element.classList.remove "dz-started"
375
376     # Called when a file is added to the queue
377     # Receives `file`
378     addedfile: (file) ->
379       @element.classList.add "dz-started" if @element == @previewsContainer
380
381       if @previewsContainer
382         file.previewElement = Dropzone.createElement @options.previewTemplate.trim()
383         file.previewTemplate = file.previewElement # Backwards compatibility
384
385         @previewsContainer.appendChild file.previewElement
386         node.textContent = @_renameFilename(file.name) for node in file.previewElement.querySelectorAll("[data-dz-name]")
387         node.innerHTML = @filesize file.size for node in file.previewElement.querySelectorAll("[data-dz-size]")
388
389         if @options.addRemoveLinks
390           file._removeLink = Dropzone.createElement """<a class="dz-remove" href="javascript:undefined;" data-dz-remove>#{@options.dictRemoveFile}</a>"""
391           file.previewElement.appendChild file._removeLink
392
393         removeFileEvent = (e) =>
394           e.preventDefault()
395           e.stopPropagation()
396           if file.status == Dropzone.UPLOADING
397             Dropzone.confirm @options.dictCancelUploadConfirmation, => @removeFile file
398           else
399             if @options.dictRemoveFileConfirmation
400               Dropzone.confirm @options.dictRemoveFileConfirmation, => @removeFile file
401             else
402               @removeFile file
403
404         removeLink.addEventListener "click", removeFileEvent for removeLink in file.previewElement.querySelectorAll("[data-dz-remove]")
405
406
407     # Called whenever a file is removed.
408     removedfile: (file) ->
409       file.previewElement?.parentNode.removeChild file.previewElement if file.previewElement
410       @_updateMaxFilesReachedClass()
411
412     # Called when a thumbnail has been generated
413     # Receives `file` and `dataUrl`
414     thumbnail: (file, dataUrl) ->
415       if file.previewElement
416         file.previewElement.classList.remove "dz-file-preview"
417         for thumbnailElement in file.previewElement.querySelectorAll("[data-dz-thumbnail]")
418           thumbnailElement.alt = file.name
419           thumbnailElement.src = dataUrl
420
421         setTimeout (=> file.previewElement.classList.add "dz-image-preview"), 1
422
423     # Called whenever an error occurs
424     # Receives `file` and `message`
425     error: (file, message) ->
426       if file.previewElement
427         file.previewElement.classList.add "dz-error"
428         message = message.error if typeof message != "String" and message.error
429         node.textContent = message for node in file.previewElement.querySelectorAll("[data-dz-errormessage]")
430
431     errormultiple: noop
432
433     # Called when a file gets processed. Since there is a cue, not all added
434     # files are processed immediately.
435     # Receives `file`
436     processing: (file) ->
437       if file.previewElement
438         file.previewElement.classList.add "dz-processing"
439         file._removeLink.textContent = @options.dictCancelUpload if file._removeLink
440
441     processingmultiple: noop
442
443     # Called whenever the upload progress gets updated.
444     # Receives `file`, `progress` (percentage 0-100) and `bytesSent`.
445     # To get the total number of bytes of the file, use `file.size`
446     uploadprogress: (file, progress, bytesSent) ->
447       if file.previewElement
448         for node in file.previewElement.querySelectorAll("[data-dz-uploadprogress]")
449           if node.nodeName is 'PROGRESS'
450             node.value = progress
451           else
452             node.style.width = "#{progress}%"
453
454     # Called whenever the total upload progress gets updated.
455     # Called with totalUploadProgress (0-100), totalBytes and totalBytesSent
456     totaluploadprogress: noop
457
458     # Called just before the file is sent. Gets the `xhr` object as second
459     # parameter, so you can modify it (for example to add a CSRF token) and a
460     # `formData` object to add additional information.
461     sending: noop
462
463     sendingmultiple: noop
464
465     # When the complete upload is finished and successful
466     # Receives `file`
467     success: (file) ->
468       file.previewElement.classList.add "dz-success" if file.previewElement
469
470     successmultiple: noop
471
472     # When the upload is canceled.
473     canceled: (file) -> @emit "error", file, "Upload canceled."
474
475     canceledmultiple: noop
476
477     # When the upload is finished, either with success or an error.
478     # Receives `file`
479     complete: (file) ->
480       file._removeLink.textContent = @options.dictRemoveFile if file._removeLink
481       file.previewElement.classList.add "dz-complete" if file.previewElement
482
483     completemultiple: noop
484
485     maxfilesexceeded: noop
486
487     maxfilesreached: noop
488
489     queuecomplete: noop
490
491     addedfiles: noop
492
493
494     # This template will be chosen when a new file is dropped.
495     previewTemplate:  """
496                       <div class="dz-preview dz-file-preview">
497                         <div class="dz-image"><img data-dz-thumbnail /></div>
498                         <div class="dz-details">
499                           <div class="dz-size"><span data-dz-size></span></div>
500                           <div class="dz-filename"><span data-dz-name></span></div>
501                         </div>
502                         <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
503                         <div class="dz-error-message"><span data-dz-errormessage></span></div>
504                         <div class="dz-success-mark">
505                           <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
506                             <title>Check</title>
507                             <defs></defs>
508                             <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
509                               <path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF" sketch:type="MSShapeGroup"></path>
510                             </g>
511                           </svg>
512                         </div>
513                         <div class="dz-error-mark">
514                           <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
515                             <title>Error</title>
516                             <defs></defs>
517                             <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
518                               <g id="Check-+-Oval-2" sketch:type="MSLayerGroup" stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475">
519                                 <path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" sketch:type="MSShapeGroup"></path>
520                               </g>
521                             </g>
522                           </svg>
523                         </div>
524                       </div>
525                       """
526
527   # global utility
528   extend = (target, objects...) ->
529     for object in objects
530       target[key] = val for key, val of object
531     target
532
533   constructor: (@element, options) ->
534     # For backwards compatibility since the version was in the prototype previously
535     @version = Dropzone.version
536
537     @defaultOptions.previewTemplate = @defaultOptions.previewTemplate.replace /\n*/g, ""
538
539     @clickableElements = [ ]
540     @listeners = [ ]
541     @files = [] # All files
542
543     @element = document.querySelector @element if typeof @element == "string"
544
545     # Not checking if instance of HTMLElement or Element since IE9 is extremely weird.
546     throw new Error "Invalid dropzone element." unless @element and @element.nodeType?
547
548     throw new Error "Dropzone already attached." if @element.dropzone
549
550     # Now add this dropzone to the instances.
551     Dropzone.instances.push @
552
553     # Put the dropzone inside the element itself.
554     @element.dropzone = @
555
556     elementOptions = Dropzone.optionsForElement(@element) ? { }
557
558     @options = extend { }, @defaultOptions, elementOptions, options ? { }
559
560     # If the browser failed, just call the fallback and leave
561     return @options.fallback.call this if @options.forceFallback or !Dropzone.isBrowserSupported()
562
563     # @options.url = @element.getAttribute "action" unless @options.url?
564     @options.url = @element.getAttribute "action" unless @options.url?
565
566     throw new Error "No URL provided." unless @options.url
567
568     throw new Error "You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated." if @options.acceptedFiles and @options.acceptedMimeTypes
569
570     # Backwards compatibility
571     if @options.acceptedMimeTypes
572       @options.acceptedFiles = @options.acceptedMimeTypes
573       delete @options.acceptedMimeTypes
574
575     @options.method = @options.method.toUpperCase()
576
577     if (fallback = @getExistingFallback()) and fallback.parentNode
578       # Remove the fallback
579       fallback.parentNode.removeChild fallback
580
581     # Display previews in the previewsContainer element or the Dropzone element unless explicitly set to false
582     if @options.previewsContainer != false
583       if @options.previewsContainer
584         @previewsContainer = Dropzone.getElement @options.previewsContainer, "previewsContainer"
585       else
586         @previewsContainer = @element
587
588     if @options.clickable
589       if @options.clickable == yes
590         @clickableElements = [ @element ]
591       else
592         @clickableElements = Dropzone.getElements @options.clickable, "clickable"
593
594
595     @init()
596
597
598   # Returns all files that have been accepted
599   getAcceptedFiles: -> file for file in @files when file.accepted
600
601   # Returns all files that have been rejected
602   # Not sure when that's going to be useful, but added for completeness.
603   getRejectedFiles: -> file for file in @files when not file.accepted
604
605   getFilesWithStatus: (status) -> file for file in @files when file.status == status
606
607   # Returns all files that are in the queue
608   getQueuedFiles: -> @getFilesWithStatus Dropzone.QUEUED
609
610   getUploadingFiles: -> @getFilesWithStatus Dropzone.UPLOADING
611
612   getAddedFiles: -> @getFilesWithStatus Dropzone.ADDED
613
614   # Files that are either queued or uploading
615   getActiveFiles: -> file for file in @files when file.status == Dropzone.UPLOADING or file.status == Dropzone.QUEUED
616
617
618   init: ->
619     # In case it isn't set already
620     @element.setAttribute("enctype", "multipart/form-data") if @element.tagName == "form"
621
622     if @element.classList.contains("dropzone") and !@element.querySelector(".dz-message")
623       @element.appendChild Dropzone.createElement """<div class="dz-default dz-message"><span>#{@options.dictDefaultMessage}</span></div>"""
624
625     if @clickableElements.length
626       setupHiddenFileInput = =>
627         @hiddenFileInput.parentNode.removeChild @hiddenFileInput if @hiddenFileInput
628         @hiddenFileInput = document.createElement "input"
629         @hiddenFileInput.setAttribute "type", "file"
630         @hiddenFileInput.setAttribute "multiple", "multiple" if !@options.maxFiles? || @options.maxFiles > 1
631         @hiddenFileInput.className = "dz-hidden-input"
632
633         @hiddenFileInput.setAttribute "accept", @options.acceptedFiles if @options.acceptedFiles?
634         @hiddenFileInput.setAttribute "capture", @options.capture if @options.capture?
635
636         # Not setting `display="none"` because some browsers don't accept clicks
637         # on elements that aren't displayed.
638         @hiddenFileInput.style.visibility = "hidden"
639         @hiddenFileInput.style.position = "absolute"
640         @hiddenFileInput.style.top = "0"
641         @hiddenFileInput.style.left = "0"
642         @hiddenFileInput.style.height = "0"
643         @hiddenFileInput.style.width = "0"
644         document.querySelector(@options.hiddenInputContainer).appendChild @hiddenFileInput
645         @hiddenFileInput.addEventListener "change", =>
646           files = @hiddenFileInput.files
647           @addFile file for file in files if files.length
648           @emit "addedfiles", files
649           setupHiddenFileInput()
650       setupHiddenFileInput()
651
652     @URL = window.URL ? window.webkitURL
653
654
655     # Setup all event listeners on the Dropzone object itself.
656     # They're not in @setupEventListeners() because they shouldn't be removed
657     # again when the dropzone gets disabled.
658     @on eventName, @options[eventName] for eventName in @events
659
660     @on "uploadprogress", => @updateTotalUploadProgress()
661
662     @on "removedfile", => @updateTotalUploadProgress()
663
664     @on "canceled", (file) => @emit "complete", file
665
666     # Emit a `queuecomplete` event if all files finished uploading.
667     @on "complete", (file) =>
668       if @getAddedFiles().length == 0 and @getUploadingFiles().length == 0 and @getQueuedFiles().length == 0
669         # This needs to be deferred so that `queuecomplete` really triggers after `complete`
670         setTimeout (=> @emit "queuecomplete"), 0
671
672
673     noPropagation = (e) ->
674       e.stopPropagation()
675       if e.preventDefault
676         e.preventDefault()
677       else
678         e.returnValue = false
679
680     # Create the listeners
681     @listeners = [
682       {
683         element: @element
684         events:
685           "dragstart": (e) =>
686             @emit "dragstart", e
687           "dragenter": (e) =>
688             noPropagation e
689             @emit "dragenter", e
690           "dragover": (e) =>
691             # Makes it possible to drag files from chrome's download bar
692             # http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar
693             # Try is required to prevent bug in Internet Explorer 11 (SCRIPT65535 exception)
694             try efct = e.dataTransfer.effectAllowed
695             e.dataTransfer.dropEffect = if 'move' == efct or 'linkMove' == efct then 'move' else 'copy'
696
697             noPropagation e
698             @emit "dragover", e
699           "dragleave": (e) =>
700             @emit "dragleave", e
701           "drop": (e) =>
702             noPropagation e
703             @drop e
704           "dragend": (e) =>
705             @emit "dragend", e
706
707           # This is disabled right now, because the browsers don't implement it properly.
708           # "paste": (e) =>
709           #   noPropagation e
710           #   @paste e
711       }
712     ]
713
714     @clickableElements.forEach (clickableElement) =>
715       @listeners.push
716         element: clickableElement
717         events:
718           "click": (evt) =>
719             # Only the actual dropzone or the message element should trigger file selection
720             if (clickableElement != @element) or (evt.target == @element or Dropzone.elementInside evt.target, @element.querySelector ".dz-message")
721               @hiddenFileInput.click() # Forward the click
722             return true
723
724     @enable()
725
726     @options.init.call @
727
728   # Not fully tested yet
729   destroy: ->
730     @disable()
731     @removeAllFiles true
732     if @hiddenFileInput?.parentNode
733       @hiddenFileInput.parentNode.removeChild @hiddenFileInput
734       @hiddenFileInput = null
735     delete @element.dropzone
736     Dropzone.instances.splice Dropzone.instances.indexOf(this), 1
737
738
739   updateTotalUploadProgress: ->
740     totalBytesSent = 0
741     totalBytes = 0
742
743     activeFiles = @getActiveFiles()
744
745     if activeFiles.length
746       for file in @getActiveFiles()
747         totalBytesSent += file.upload.bytesSent
748         totalBytes += file.upload.total
749       totalUploadProgress = 100 * totalBytesSent / totalBytes
750     else
751       totalUploadProgress = 100
752
753     @emit "totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent
754
755   # @options.paramName can be a function taking one parameter rather than a string.
756   # A parameter name for a file is obtained simply by calling this with an index number.
757   _getParamName: (n) ->
758     if typeof @options.paramName is "function"
759       @options.paramName n
760     else
761       "#{@options.paramName}#{if @options.uploadMultiple then "[#{n}]" else ""}"
762
763   # If @options.renameFilename is a function,
764   # the function will be used to rename the file.name before appending it to the formData
765   _renameFilename: (name) ->
766     return name unless typeof @options.renameFilename is "function"
767     @options.renameFilename name
768
769   # Returns a form that can be used as fallback if the browser does not support DragnDrop
770   #
771   # If the dropzone is already a form, only the input field and button are returned. Otherwise a complete form element is provided.
772   # This code has to pass in IE7 :(
773   getFallbackForm: ->
774     return existingFallback if existingFallback = @getExistingFallback()
775
776     fieldsString = """<div class="dz-fallback">"""
777     fieldsString += """<p>#{@options.dictFallbackText}</p>""" if @options.dictFallbackText
778     fieldsString += """<input type="file" name="#{@_getParamName 0}" #{if @options.uploadMultiple then 'multiple="multiple"' } /><input type="submit" value="Upload!"></div>"""
779
780     fields = Dropzone.createElement fieldsString
781     if @element.tagName isnt "FORM"
782       form = Dropzone.createElement("""<form action="#{@options.url}" enctype="multipart/form-data" method="#{@options.method}"></form>""")
783       form.appendChild fields
784     else
785       # Make sure that the enctype and method attributes are set properly
786       @element.setAttribute "enctype", "multipart/form-data"
787       @element.setAttribute "method", @options.method
788     form ? fields
789
790
791   # Returns the fallback elements if they exist already
792   #
793   # This code has to pass in IE7 :(
794   getExistingFallback: ->
795     getFallback = (elements) -> return el for el in elements when /(^| )fallback($| )/.test el.className
796
797     for tagName in [ "div", "form" ]
798       return fallback if fallback = getFallback @element.getElementsByTagName tagName
799
800
801   # Activates all listeners stored in @listeners
802   setupEventListeners: ->
803     for elementListeners in @listeners
804       elementListeners.element.addEventListener event, listener, false for event, listener of elementListeners.events
805
806
807   # Deactivates all listeners stored in @listeners
808   removeEventListeners: ->
809     for elementListeners in @listeners
810       elementListeners.element.removeEventListener event, listener, false for event, listener of elementListeners.events
811
812   # Removes all event listeners and cancels all files in the queue or being processed.
813   disable: ->
814     @clickableElements.forEach (element) -> element.classList.remove "dz-clickable"
815     @removeEventListeners()
816
817     @cancelUpload file for file in @files
818
819   enable: ->
820     @clickableElements.forEach (element) -> element.classList.add "dz-clickable"
821     @setupEventListeners()
822
823   # Returns a nicely formatted filesize
824   filesize: (size) ->
825     selectedSize = 0
826     selectedUnit = "b"
827
828     if size > 0
829       units = [ 'TB', 'GB', 'MB', 'KB', 'b' ]
830
831       for unit, i in units
832         cutoff = Math.pow(@options.filesizeBase, 4 - i) / 10
833
834         if size >= cutoff
835           selectedSize = size / Math.pow(@options.filesizeBase, 4 - i)
836           selectedUnit = unit
837           break
838
839       selectedSize = Math.round(10 * selectedSize) / 10 # Cutting of digits
840
841     "<strong>#{selectedSize}</strong> #{selectedUnit}"
842
843
844   # Adds or removes the `dz-max-files-reached` class from the form.
845   _updateMaxFilesReachedClass: ->
846     if @options.maxFiles? and @getAcceptedFiles().length >= @options.maxFiles
847       @emit 'maxfilesreached', @files if @getAcceptedFiles().length == @options.maxFiles
848       @element.classList.add "dz-max-files-reached"
849     else
850       @element.classList.remove "dz-max-files-reached"
851
852
853
854   drop: (e) ->
855     return unless e.dataTransfer
856     @emit "drop", e
857
858     files = e.dataTransfer.files
859     @emit "addedfiles", files
860
861     # Even if it's a folder, files.length will contain the folders.
862     if files.length
863       items = e.dataTransfer.items
864       if items and items.length and (items[0].webkitGetAsEntry?)
865         # The browser supports dropping of folders, so handle items instead of files
866         @_addFilesFromItems items
867       else
868         @handleFiles files
869     return
870
871   paste: (e) ->
872     return unless e?.clipboardData?.items?
873
874     @emit "paste", e
875     items = e.clipboardData.items
876
877     @_addFilesFromItems items if items.length
878
879
880   handleFiles: (files) ->
881     @addFile file for file in files
882
883   # When a folder is dropped (or files are pasted), items must be handled
884   # instead of files.
885   _addFilesFromItems: (items) ->
886     for item in items
887       if item.webkitGetAsEntry? and entry = item.webkitGetAsEntry()
888         if entry.isFile
889           @addFile item.getAsFile()
890         else if entry.isDirectory
891           # Append all files from that directory to files
892           @_addFilesFromDirectory entry, entry.name
893       else if item.getAsFile?
894         if !item.kind? or item.kind == "file"
895           @addFile item.getAsFile()
896
897
898   # Goes through the directory, and adds each file it finds recursively
899   _addFilesFromDirectory: (directory, path) ->
900     dirReader = directory.createReader()
901
902     errorHandler = (error) -> console?.log? error
903
904     readEntries = () =>
905       dirReader.readEntries (entries) =>
906         if entries.length > 0
907           for entry in entries
908             if entry.isFile
909               entry.file (file) =>
910                 return if @options.ignoreHiddenFiles and file.name.substring(0, 1) is '.'
911                 file.fullPath = "#{path}/#{file.name}"
912                 @addFile file
913             else if entry.isDirectory
914               @_addFilesFromDirectory entry, "#{path}/#{entry.name}"
915
916           # Recursively call readEntries() again, since browser only handle
917           # the first 100 entries.
918           # See: https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader#readEntries
919           readEntries()
920         return null
921       , errorHandler
922
923     readEntries()
924
925
926
927   # If `done()` is called without argument the file is accepted
928   # If you call it with an error message, the file is rejected
929   # (This allows for asynchronous validation)
930   #
931   # This function checks the filesize, and if the file.type passes the
932   # `acceptedFiles` check.
933   accept: (file, done) ->
934     if file.size > @options.maxFilesize * 1024 * 1024
935       done @options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", @options.maxFilesize)
936     else unless Dropzone.isValidFile file, @options.acceptedFiles
937       done @options.dictInvalidFileType
938     else if @options.maxFiles? and @getAcceptedFiles().length >= @options.maxFiles
939       done @options.dictMaxFilesExceeded.replace "{{maxFiles}}", @options.maxFiles
940       @emit "maxfilesexceeded", file
941     else
942       @options.accept.call this, file, done
943
944   addFile: (file) ->
945     file.upload =
946       progress: 0
947       # Setting the total upload size to file.size for the beginning
948       # It's actual different than the size to be transmitted.
949       total: file.size
950       bytesSent: 0
951     @files.push file
952
953     file.status = Dropzone.ADDED
954
955     @emit "addedfile", file
956
957     @_enqueueThumbnail file
958
959     @accept file, (error) =>
960       if error
961         file.accepted = false
962         @_errorProcessing [ file ], error # Will set the file.status
963       else
964         file.accepted = true
965         @enqueueFile file if @options.autoQueue # Will set .accepted = true
966       @_updateMaxFilesReachedClass()
967
968
969   # Wrapper for enqueueFile
970   enqueueFiles: (files) -> @enqueueFile file for file in files; null
971
972   enqueueFile: (file) ->
973     if file.status == Dropzone.ADDED and file.accepted == true
974       file.status = Dropzone.QUEUED
975       if @options.autoProcessQueue
976         setTimeout (=> @processQueue()), 0 # Deferring the call
977     else
978       throw new Error "This file can't be queued because it has already been processed or was rejected."
979
980
981   _thumbnailQueue: [ ]
982   _processingThumbnail: no
983   _enqueueThumbnail: (file) ->
984     if @options.createImageThumbnails and file.type.match(/image.*/) and file.size <= @options.maxThumbnailFilesize * 1024 * 1024
985       @_thumbnailQueue.push(file)
986       setTimeout (=> @_processThumbnailQueue()), 0 # Deferring the call
987
988   _processThumbnailQueue: ->
989     return if @_processingThumbnail or @_thumbnailQueue.length == 0
990
991     @_processingThumbnail = yes
992     @createThumbnail @_thumbnailQueue.shift(), =>
993       @_processingThumbnail = no
994       @_processThumbnailQueue()
995
996
997   # Can be called by the user to remove a file
998   removeFile: (file) ->
999     @cancelUpload file if file.status == Dropzone.UPLOADING
1000     @files = without @files, file
1001
1002     @emit "removedfile", file
1003     @emit "reset" if @files.length == 0
1004
1005   # Removes all files that aren't currently processed from the list
1006   removeAllFiles: (cancelIfNecessary = off) ->
1007     # Create a copy of files since removeFile() changes the @files array.
1008     for file in @files.slice()
1009       @removeFile file if file.status != Dropzone.UPLOADING || cancelIfNecessary
1010     return null
1011
1012   createThumbnail: (file, callback) ->
1013
1014     fileReader = new FileReader
1015
1016     fileReader.onload = =>
1017
1018       # Don't bother creating a thumbnail for SVG images since they're vector
1019       if file.type == "image/svg+xml"
1020         @emit "thumbnail", file, fileReader.result
1021         callback() if callback?
1022         return
1023
1024       @createThumbnailFromUrl file, fileReader.result, callback
1025
1026     fileReader.readAsDataURL file
1027
1028   createThumbnailFromUrl: (file, imageUrl, callback, crossOrigin) ->
1029     # Not using `new Image` here because of a bug in latest Chrome versions.
1030     # See https://github.com/enyo/dropzone/pull/226
1031     img = document.createElement "img"
1032
1033     img.crossOrigin = crossOrigin if crossOrigin
1034
1035     img.onload = =>
1036       file.width = img.width
1037       file.height = img.height
1038
1039       resizeInfo = @options.resize.call @, file
1040
1041       resizeInfo.trgWidth ?= resizeInfo.optWidth
1042       resizeInfo.trgHeight ?= resizeInfo.optHeight
1043
1044       canvas = document.createElement "canvas"
1045       ctx = canvas.getContext "2d"
1046       canvas.width = resizeInfo.trgWidth
1047       canvas.height = resizeInfo.trgHeight
1048
1049       # This is a bugfix for iOS' scaling bug.
1050       drawImageIOSFix ctx, img, resizeInfo.srcX ? 0, resizeInfo.srcY ? 0, resizeInfo.srcWidth, resizeInfo.srcHeight, resizeInfo.trgX ? 0, resizeInfo.trgY ? 0, resizeInfo.trgWidth, resizeInfo.trgHeight
1051
1052       thumbnail = canvas.toDataURL "image/png"
1053
1054       @emit "thumbnail", file, thumbnail
1055       callback() if callback?
1056
1057     img.onerror = callback if callback?
1058
1059     img.src = imageUrl
1060
1061
1062   # Goes through the queue and processes files if there aren't too many already.
1063   processQueue: ->
1064     parallelUploads = @options.parallelUploads
1065     processingLength = @getUploadingFiles().length
1066     i = processingLength
1067
1068     # There are already at least as many files uploading than should be
1069     return if processingLength >= parallelUploads
1070
1071     queuedFiles = @getQueuedFiles()
1072
1073     return unless queuedFiles.length > 0
1074
1075     if @options.uploadMultiple
1076       # The files should be uploaded in one request
1077       @processFiles queuedFiles.slice 0, (parallelUploads - processingLength)
1078     else
1079       while i < parallelUploads
1080         return unless queuedFiles.length # Nothing left to process
1081         @processFile queuedFiles.shift()
1082         i++
1083
1084
1085   # Wrapper for `processFiles`
1086   processFile: (file) -> @processFiles [ file ]
1087
1088
1089   # Loads the file, then calls finishedLoading()
1090   processFiles: (files) ->
1091     for file in files
1092       file.processing = yes # Backwards compatibility
1093       file.status = Dropzone.UPLOADING
1094
1095       @emit "processing", file
1096
1097     @emit "processingmultiple", files if @options.uploadMultiple
1098
1099     @uploadFiles files
1100
1101
1102
1103   _getFilesWithXhr: (xhr) -> files = (file for file in @files when file.xhr == xhr)
1104
1105
1106   # Cancels the file upload and sets the status to CANCELED
1107   # **if** the file is actually being uploaded.
1108   # If it's still in the queue, the file is being removed from it and the status
1109   # set to CANCELED.
1110   cancelUpload: (file) ->
1111     if file.status == Dropzone.UPLOADING
1112       groupedFiles = @_getFilesWithXhr file.xhr
1113       groupedFile.status = Dropzone.CANCELED for groupedFile in groupedFiles
1114       file.xhr.abort()
1115       @emit "canceled", groupedFile for groupedFile in groupedFiles
1116       @emit "canceledmultiple", groupedFiles if @options.uploadMultiple
1117
1118     else if file.status in [ Dropzone.ADDED, Dropzone.QUEUED ]
1119       file.status = Dropzone.CANCELED
1120       @emit "canceled", file
1121       @emit "canceledmultiple", [ file ] if @options.uploadMultiple
1122
1123     @processQueue() if @options.autoProcessQueue
1124
1125   resolveOption = (option, args...) ->
1126     if typeof option == 'function'
1127       return option.apply(@, args)
1128     option
1129
1130   # Wrapper for uploadFiles()
1131   uploadFile: (file) -> @uploadFiles [ file ]
1132
1133   uploadFiles: (files) ->
1134     xhr = new XMLHttpRequest()
1135
1136     # Put the xhr object in the file objects to be able to reference it later.
1137     file.xhr = xhr for file in files
1138
1139     method = resolveOption @options.method, files
1140     url = resolveOption @options.url, files
1141     xhr.open method, url, true
1142
1143     # Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
1144     xhr.withCredentials = !!@options.withCredentials
1145
1146
1147     response = null
1148
1149     handleError = =>
1150       for file in files
1151         @_errorProcessing files, response || @options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr
1152
1153
1154     updateProgress = (e) =>
1155       if e?
1156         progress = 100 * e.loaded / e.total
1157
1158         for file in files
1159           file.upload =
1160             progress: progress
1161             total: e.total
1162             bytesSent: e.loaded
1163       else
1164         # Called when the file finished uploading
1165
1166         allFilesFinished = yes
1167
1168         progress = 100
1169
1170         for file in files
1171           allFilesFinished = no unless file.upload.progress == 100 and file.upload.bytesSent == file.upload.total
1172           file.upload.progress = progress
1173           file.upload.bytesSent = file.upload.total
1174
1175         # Nothing to do, all files already at 100%
1176         return if allFilesFinished
1177
1178       for file in files
1179         @emit "uploadprogress", file, progress, file.upload.bytesSent
1180
1181     xhr.onload = (e) =>
1182       return if files[0].status == Dropzone.CANCELED
1183
1184       return unless xhr.readyState is 4
1185
1186       response = xhr.responseText
1187
1188       if xhr.getResponseHeader("content-type") and ~xhr.getResponseHeader("content-type").indexOf "application/json"
1189         try
1190           response = JSON.parse response
1191         catch e
1192           response = "Invalid JSON response from server."
1193
1194       updateProgress()
1195
1196       unless 200 <= xhr.status < 300
1197         handleError()
1198       else
1199         @_finished files, response, e
1200
1201     xhr.onerror = =>
1202       return if files[0].status == Dropzone.CANCELED
1203       handleError()
1204
1205     # Some browsers do not have the .upload property
1206     progressObj = xhr.upload ? xhr
1207     progressObj.onprogress = updateProgress
1208
1209     headers =
1210       "Accept": "application/json",
1211       "Cache-Control": "no-cache",
1212       "X-Requested-With": "XMLHttpRequest",
1213
1214     extend headers, @options.headers if @options.headers
1215
1216     for headerName, headerValue of headers
1217       xhr.setRequestHeader headerName, headerValue if headerValue
1218
1219     formData = new FormData()
1220
1221     # Adding all @options parameters
1222     formData.append key, value for key, value of @options.params if @options.params
1223
1224     # Let the user add additional data if necessary
1225     @emit "sending", file, xhr, formData for file in files
1226     @emit "sendingmultiple", files, xhr, formData if @options.uploadMultiple
1227
1228
1229     # Take care of other input elements
1230     if @element.tagName == "FORM"
1231       for input in @element.querySelectorAll "input, textarea, select, button"
1232         inputName = input.getAttribute "name"
1233         inputType = input.getAttribute "type"
1234
1235         if input.tagName == "SELECT" and input.hasAttribute "multiple"
1236           # Possibly multiple values
1237           formData.append inputName, option.value for option in input.options when option.selected
1238         else if !inputType or (inputType.toLowerCase() not in [ "checkbox", "radio" ]) or input.checked
1239           formData.append inputName, input.value
1240
1241
1242     # Finally add the file
1243     # Has to be last because some servers (eg: S3) expect the file to be the
1244     # last parameter
1245     formData.append @_getParamName(i), files[i], @_renameFilename(files[i].name) for i in [0..files.length-1]
1246
1247     @submitRequest xhr, formData, files
1248
1249   submitRequest: (xhr, formData, files) ->
1250     xhr.send formData
1251
1252   # Called internally when processing is finished.
1253   # Individual callbacks have to be called in the appropriate sections.
1254   _finished: (files, responseText, e) ->
1255     for file in files
1256       file.status = Dropzone.SUCCESS
1257       @emit "success", file, responseText, e
1258       @emit "complete", file
1259     if @options.uploadMultiple
1260       @emit "successmultiple", files, responseText, e
1261       @emit "completemultiple", files
1262
1263     @processQueue() if @options.autoProcessQueue
1264
1265   # Called internally when processing is finished.
1266   # Individual callbacks have to be called in the appropriate sections.
1267   _errorProcessing: (files, message, xhr) ->
1268     for file in files
1269       file.status = Dropzone.ERROR
1270       @emit "error", file, message, xhr
1271       @emit "complete", file
1272     if @options.uploadMultiple
1273       @emit "errormultiple", files, message, xhr
1274       @emit "completemultiple", files
1275
1276     @processQueue() if @options.autoProcessQueue
1277
1278
1279
1280 Dropzone.version = "4.3.0"
1281
1282
1283 # This is a map of options for your different dropzones. Add configurations
1284 # to this object for your different dropzone elemens.
1285 #
1286 # Example:
1287 #
1288 #     Dropzone.options.myDropzoneElementId = { maxFilesize: 1 };
1289 #
1290 # To disable autoDiscover for a specific element, you can set `false` as an option:
1291 #
1292 #     Dropzone.options.myDisabledElementId = false;
1293 #
1294 # And in html:
1295 #
1296 #     <form action="/upload" id="my-dropzone-element-id" class="dropzone"></form>
1297 Dropzone.options = { }
1298
1299
1300 # Returns the options for an element or undefined if none available.
1301 Dropzone.optionsForElement = (element) ->
1302   # Get the `Dropzone.options.elementId` for this element if it exists
1303   if element.getAttribute("id") then Dropzone.options[camelize element.getAttribute "id"] else undefined
1304
1305
1306 # Holds a list of all dropzone instances
1307 Dropzone.instances = [ ]
1308
1309 # Returns the dropzone for given element if any
1310 Dropzone.forElement = (element) ->
1311   element = document.querySelector element if typeof element == "string"
1312   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?
1313   return element.dropzone
1314
1315
1316 # Set to false if you don't want Dropzone to automatically find and attach to .dropzone elements.
1317 Dropzone.autoDiscover = on
1318
1319 # Looks for all .dropzone elements and creates a dropzone for them
1320 Dropzone.discover = ->
1321   if document.querySelectorAll
1322     dropzones = document.querySelectorAll ".dropzone"
1323   else
1324     dropzones = [ ]
1325     # IE :(
1326     checkElements = (elements) ->
1327       for el in elements
1328         dropzones.push el if /(^| )dropzone($| )/.test el.className
1329     checkElements document.getElementsByTagName "div"
1330     checkElements document.getElementsByTagName "form"
1331
1332   for dropzone in dropzones
1333     # Create a dropzone unless auto discover has been disabled for specific element
1334     new Dropzone dropzone unless Dropzone.optionsForElement(dropzone) == false
1335
1336
1337
1338 # Since the whole Drag'n'Drop API is pretty new, some browsers implement it,
1339 # but not correctly.
1340 # So I created a blacklist of userAgents. Yes, yes. Browser sniffing, I know.
1341 # But what to do when browsers *theoretically* support an API, but crash
1342 # when using it.
1343 #
1344 # This is a list of regular expressions tested against navigator.userAgent
1345 #
1346 # ** It should only be used on browser that *do* support the API, but
1347 # incorrectly **
1348 #
1349 Dropzone.blacklistedBrowsers = [
1350   # The mac os version of opera 12 seems to have a problem with the File drag'n'drop API.
1351   /opera.*Macintosh.*version\/12/i
1352   # /MSIE\ 10/i
1353 ]
1354
1355
1356 # Checks if the browser is supported
1357 Dropzone.isBrowserSupported = ->
1358   capableBrowser = yes
1359
1360   if window.File and window.FileReader and window.FileList and window.Blob and window.FormData and document.querySelector
1361     unless "classList" of document.createElement "a"
1362       capableBrowser = no
1363     else
1364       # The browser supports the API, but may be blacklisted.
1365       for regex in Dropzone.blacklistedBrowsers
1366         if regex.test navigator.userAgent
1367           capableBrowser = no
1368           continue
1369   else
1370     capableBrowser = no
1371
1372   capableBrowser
1373
1374
1375
1376
1377 # Returns an array without the rejected item
1378 without = (list, rejectedItem) -> item for item in list when item isnt rejectedItem
1379
1380 # abc-def_ghi -> abcDefGhi
1381 camelize = (str) -> str.replace /[\-_](\w)/g, (match) -> match.charAt(1).toUpperCase()
1382
1383 # Creates an element from string
1384 Dropzone.createElement = (string) ->
1385   div = document.createElement "div"
1386   div.innerHTML = string
1387   div.childNodes[0]
1388
1389 # Tests if given element is inside (or simply is) the container
1390 Dropzone.elementInside = (element, container) ->
1391   return yes if element == container # Coffeescript doesn't support do/while loops
1392   return yes while element = element.parentNode when element == container
1393   return no
1394
1395
1396
1397 Dropzone.getElement = (el, name) ->
1398   if typeof el == "string"
1399     element = document.querySelector el
1400   else if el.nodeType?
1401     element = el
1402   throw new Error "Invalid `#{name}` option provided. Please provide a CSS selector or a plain HTML element." unless element?
1403   return element
1404
1405
1406 Dropzone.getElements = (els, name) ->
1407   if els instanceof Array
1408     elements = [ ]
1409     try
1410       elements.push @getElement el, name for el in els
1411     catch e
1412       elements = null
1413   else if typeof els == "string"
1414     elements = [ ]
1415     elements.push el for el in document.querySelectorAll els
1416   else if els.nodeType?
1417     elements = [ els ]
1418
1419   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
1420
1421   return elements
1422
1423 # Asks the user the question and calls accepted or rejected accordingly
1424 #
1425 # The default implementation just uses `window.confirm` and then calls the
1426 # appropriate callback.
1427 Dropzone.confirm = (question, accepted, rejected) ->
1428   if window.confirm question
1429     accepted()
1430   else if rejected?
1431     rejected()
1432
1433 # Validates the mime type like this:
1434 #
1435 # https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept
1436 Dropzone.isValidFile = (file, acceptedFiles) ->
1437   return yes unless acceptedFiles # If there are no accepted mime types, it's OK
1438   acceptedFiles = acceptedFiles.split ","
1439
1440   mimeType = file.type
1441   baseMimeType = mimeType.replace /\/.*$/, ""
1442
1443   for validType in acceptedFiles
1444     validType = validType.trim()
1445     if validType.charAt(0) == "."
1446       return yes if file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) != -1
1447     else if /\/\*$/.test validType
1448       # This is something like a image/* mime type
1449       return yes if baseMimeType == validType.replace /\/.*$/, ""
1450     else
1451       return yes if mimeType == validType
1452
1453   return no
1454
1455
1456 # Augment jQuery
1457 if jQuery?
1458   jQuery.fn.dropzone = (options) ->
1459     this.each -> new Dropzone this, options
1460
1461
1462
1463
1464 if module?
1465   module.exports = Dropzone
1466 else
1467   window.Dropzone = Dropzone
1468
1469
1470
1471
1472
1473 # Dropzone file status codes
1474 Dropzone.ADDED = "added"
1475
1476 Dropzone.QUEUED = "queued"
1477 # For backwards compatibility. Now, if a file is accepted, it's either queued
1478 # or uploading.
1479 Dropzone.ACCEPTED = Dropzone.QUEUED
1480
1481 Dropzone.UPLOADING = "uploading"
1482 Dropzone.PROCESSING = Dropzone.UPLOADING # alias
1483
1484 Dropzone.CANCELED = "canceled"
1485 Dropzone.ERROR = "error"
1486 Dropzone.SUCCESS = "success"
1487
1488
1489
1490
1491
1492 ###
1493
1494 Bugfix for iOS 6 and 7
1495 Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios
1496 based on the work of https://github.com/stomita/ios-imagefile-megapixel
1497
1498 ###
1499
1500 # Detecting vertical squash in loaded image.
1501 # Fixes a bug which squash image vertically while drawing into canvas for some images.
1502 # This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel
1503 detectVerticalSquash = (img) ->
1504   iw = img.naturalWidth
1505   ih = img.naturalHeight
1506   canvas = document.createElement("canvas")
1507   canvas.width = 1
1508   canvas.height = ih
1509   ctx = canvas.getContext("2d")
1510   ctx.drawImage img, 0, 0
1511   data = ctx.getImageData(0, 0, 1, ih).data
1512
1513
1514   # search image edge pixel position in case it is squashed vertically.
1515   sy = 0
1516   ey = ih
1517   py = ih
1518   while py > sy
1519     alpha = data[(py - 1) * 4 + 3]
1520
1521     if alpha is 0 then ey = py else sy = py
1522
1523     py = (ey + sy) >> 1
1524   ratio = (py / ih)
1525
1526   if (ratio is 0) then 1 else ratio
1527
1528 # A replacement for context.drawImage
1529 # (args are for source and destination).
1530 drawImageIOSFix = (ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) ->
1531   vertSquashRatio = detectVerticalSquash img
1532   ctx.drawImage img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio
1533
1534
1535
1536
1537
1538
1539
1540
1541 ###
1542 # contentloaded.js
1543 #
1544 # Author: Diego Perini (diego.perini at gmail.com)
1545 # Summary: cross-browser wrapper for DOMContentLoaded
1546 # Updated: 20101020
1547 # License: MIT
1548 # Version: 1.2
1549 #
1550 # URL:
1551 # http://javascript.nwbox.com/ContentLoaded/
1552 # http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
1553 ###
1554
1555 # @win window reference
1556 # @fn function reference
1557 contentLoaded = (win, fn) ->
1558   done = false
1559   top = true
1560   doc = win.document
1561   root = doc.documentElement
1562   add = (if doc.addEventListener then "addEventListener" else "attachEvent")
1563   rem = (if doc.addEventListener then "removeEventListener" else "detachEvent")
1564   pre = (if doc.addEventListener then "" else "on")
1565   init = (e) ->
1566     return  if e.type is "readystatechange" and doc.readyState isnt "complete"
1567     ((if e.type is "load" then win else doc))[rem] pre + e.type, init, false
1568     fn.call win, e.type or e  if not done and (done = true)
1569
1570   poll = ->
1571     try
1572       root.doScroll "left"
1573     catch e
1574       setTimeout poll, 50
1575       return
1576     init "poll"
1577
1578   unless doc.readyState is "complete"
1579     if doc.createEventObject and root.doScroll
1580       try
1581         top = not win.frameElement
1582       poll()  if top
1583     doc[add] pre + "DOMContentLoaded", init, false
1584     doc[add] pre + "readystatechange", init, false
1585     win[add] pre + "load", init, false
1586
1587
1588 # As a single function to be able to write tests.
1589 Dropzone._autoDiscoverFunction = -> Dropzone.discover() if Dropzone.autoDiscover
1590 contentLoaded window, Dropzone._autoDiscoverFunction