0cdeeee21fdf12d7fcb35d2fdbe6b43318ce6204
[moodle.git] / lib / form / dndupload.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Javascript library for enableing a drag and drop upload interface
18  *
19  * @package    moodlecore
20  * @subpackage form
21  * @copyright  2011 Davo Smith
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 M.form_dndupload = {}
27 M.form_dndupload.init = function(Y, options) {
28     var dnduploadhelper = {
29         // YUI object.
30         Y: null,
31         // URL for upload requests
32         url: M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload',
33         // options may include: itemid, acceptedtypes, maxfiles, maxbytes, clientid, repositoryid, author
34         options: {},
35         // itemid used for repository upload
36         itemid: null,
37         // accepted filetypes accepted by this form passed to repository
38         acceptedtypes: [],
39         // maximum size of files allowed in this form
40         maxbytes: 0,
41         // unqiue id of this form field used for html elements
42         clientid: '',
43         // upload repository id, used for upload
44         repositoryid: 0,
45         // container which holds the node which recieves drag events
46         container: null,
47         // filemanager element we are working with
48         filemanager: null,
49         // callback  to filepicker element to refesh when uploaded
50         callback: null,
51         // Nasty hack to distinguish between dragenter(first entry),
52         // dragenter+dragleave(moving between child elements) and dragleave (leaving element)
53         entercount: 0,
54         pageentercount: 0,
56         /**
57          * Initalise the drag and drop upload interface
58          * Note: one and only one of options.filemanager and options.formcallback must be defined
59          *
60          * @param Y the YUI object
61          * @param object options {
62          *            itemid: itemid used for repository upload in this form
63          *            acceptdtypes: accepted filetypes by this form
64          *            maxfiles: maximum number of files this form allows
65          *            maxbytes: maximum size of files allowed in this form
66          *            clientid: unqiue id of this form field used for html elements
67          *            containerid: htmlid of container
68          *            repositories: array of repository objects passed from filepicker
69          *            filemanager: filemanager element we are working with
70          *            formcallback: callback  to filepicker element to refesh when uploaded
71          *          }
72          */
73         init: function(Y, options) {
74             this.Y = Y;
76             if (!this.browser_supported()) {
77                 Y.one('body').addClass('dndnotsupported');
78                 return; // Browser does not support the required functionality
79             }
80             Y.one('body').addClass('dndsupported');
82             // try and retrieve enabled upload repository
83             this.repositoryid = this.get_upload_repositoryid(options.repositories);
85             if (!this.repositoryid) {
86                 return; // no upload repository is enabled to upload to
87             }
89             this.options = options;
90             this.acceptedtypes = options.acceptedtypes;
91             this.clientid = options.clientid;
92             this.maxbytes = options.maxbytes;
93             this.itemid = options.itemid;
94             this.author = options.author;
95             this.container = this.Y.one('#'+options.containerid);
97             if (options.filemanager) {
98                 // Needed to tell the filemanager to redraw when files uploaded
99                 // and to check how many files are already uploaded
100                 this.filemanager = options.filemanager;
101             } else if (options.formcallback) {
103                 // Needed to tell the filepicker to update when a new
104                 // file is uploaded
105                 this.callback = options.formcallback;
106             } else {
107                 if (M.cfg.developerdebug) {
108                     alert('dndupload: Need to define either options.filemanager or options.formcallback');
109                 }
110                 return;
111             }
113             this.init_events();
114             this.init_page_events();
115         },
117         /**
118          * Check the browser has the required functionality
119          * @return true if browser supports drag/drop upload
120          */
121         browser_supported: function() {
123             if (typeof FileReader == 'undefined') {
124                 return false;
125             }
126             if (typeof FormData == 'undefined') {
127                 return false;
128             }
129             return true;
130         },
132         /**
133          * Get upload repoistory from array of enabled repositories
134          *
135          * @param array repositories repository objects passed from filepicker
136          * @param returns int id of upload repository or false if not found
137          */
138         get_upload_repositoryid: function(repositories) {
139             for (var i in repositories) {
140                 if (repositories[i].type == "upload") {
141                     return repositories[i].id;
142                 }
143             }
145             return false;
146         },
148         /**
149          * Initialise drag events on node container, all events need
150          * to be processed for drag and drop to work
151          */
152         init_events: function() {
153             this.Y.on('dragenter', this.drag_enter, this.container, this);
154             this.Y.on('dragleave', this.drag_leave, this.container, this);
155             this.Y.on('dragover',  this.drag_over,  this.container, this);
156             this.Y.on('drop',      this.drop,      this.container, this);
157         },
159         /**
160          * Initialise whole-page events (to show / hide the 'drop files here'
161          * message)
162          */
163         init_page_events: function() {
164             this.Y.on('dragenter', this.drag_enter_page, 'body', this);
165             this.Y.on('dragleave', this.drag_leave_page, 'body', this);
166         },
168         /**
169          * Check if the filemanager / filepicker is disabled
170          * @return bool - true if disabled
171          */
172         is_disabled: function() {
173             return this.container.hasClass('disabled');
174         },
176         /**
177          * Show the 'drop files here' message when file(s) are dragged
178          * onto the page
179          */
180         drag_enter_page: function(e) {
181             if (this.is_disabled()) {
182                 return false;
183             }
184             if (!this.has_files(e)) {
185                 return false;
186             }
188             this.pageentercount++;
189             if (this.pageentercount >= 2) {
190                 this.pageentercount = 2;
191                 return false;
192             }
194             this.show_drop_target();
196             return false;
197         },
199         /**
200          * Hide the 'drop files here' message when file(s) are dragged off
201          * the page again
202          */
203         drag_leave_page: function(e) {
204             this.pageentercount--;
205             if (this.pageentercount == 1) {
206                 return false;
207             }
208             this.pageentercount = 0;
210             this.hide_drop_target();
212             return false;
213         },
215         /**
216          * Check if the drag contents are valid and then call
217          * preventdefault / stoppropagation to let the browser know
218          * we will handle this drag/drop
219          *
220          * @param e event object
221          * @return boolean true if a valid file drag event
222          */
223         check_drag: function(e) {
224             if (this.is_disabled()) {
225                 return false;
226             }
227             if (!this.has_files(e)) {
228                 return false;
229             }
231             e.preventDefault();
232             e.stopPropagation();
234             return true;
235         },
237         /**
238          * Handle a dragenter event, highlight the destination node
239          * when a suitable drag event occurs
240          */
241         drag_enter: function(e) {
242             if (!this.check_drag(e)) {
243                 return true;
244             }
246             this.entercount++;
247             if (this.entercount >= 2) {
248                 this.entercount = 2; // Just moved over a child element - nothing to do
249                 return false;
250             }
252             // These lines are needed if the user has dragged something directly
253             // from application onto the 'fileupload' box, without crossing another
254             // part of the page first
255             this.pageentercount = 2;
256             this.show_drop_target();
258             this.show_upload_ready();
259             return false;
260         },
262         /**
263          * Handle a dragleave event, Remove the highlight if dragged from
264          * node
265          */
266         drag_leave: function(e) {
267             if (!this.check_drag(e)) {
268                 return true;
269             }
271             this.entercount--;
272             if (this.entercount == 1) {
273                 return false; // Just moved over a child element - nothing to do
274             }
276             this.entercount = 0;
277             this.hide_upload_ready();
278             return false;
279         },
281         /**
282          * Handle a dragover event. Required to intercept to prevent the browser from
283          * handling the drag and drop event as normal
284          */
285         drag_over: function(e) {
286             if (!this.check_drag(e)) {
287                 return true;
288             }
290             return false;
291         },
293         /**
294          * Handle a drop event.  Remove the highlight and then upload each
295          * of the files (until we reach the file limit, or run out of files)
296          */
297         drop: function(e) {
298             if (!this.check_drag(e, true)) {
299                 return true;
300             }
302             this.entercount = 0;
303             this.pageentercount = 0;
304             this.hide_upload_ready();
305             this.hide_drop_target();
307             var files = e._event.dataTransfer.files;
308             if (this.filemanager) {
309                 var options = {
310                     files: files,
311                     options: this.options,
312                     repositoryid: this.repositoryid,
313                     currentfilecount: this.filemanager.filecount, // All files uploaded.
314                     currentfiles: this.filemanager.options.list, // Only the current folder.
315                     callback: Y.bind('update_filemanager', this)
316                 };
317                 var uploader = new dnduploader(options);
318                 uploader.start_upload();
319             } else {
320                 if (files.length >= 1) {
321                     options = {
322                         files:[files[0]],
323                         options: this.options,
324                         repositoryid: this.repositoryid,
325                         currentfilecount: 0,
326                         currentfiles: [],
327                         callback: Y.bind('callback', this)
328                     };
329                     uploader = new dnduploader(options);
330                     uploader.start_upload();
331                 }
332             }
334             return false;
335         },
337         /**
338          * Check to see if the drag event has any files in it
339          *
340          * @param e event object
341          * @return boolean true if event has files
342          */
343         has_files: function(e) {
344             var types = e._event.dataTransfer.types;
345             for (var i=0; i<types.length; i++) {
346                 if (types[i] == 'Files') {
347                     return true;
348                 }
349             }
350             return false;
351         },
353         /**
354          * Highlight the area where files could be dropped
355          */
356         show_drop_target: function() {
357             this.container.addClass('dndupload-ready');
358         },
360         hide_drop_target: function() {
361             this.container.removeClass('dndupload-ready');
362         },
364         /**
365          * Highlight the destination node (ready to drop)
366          */
367         show_upload_ready: function() {
368             this.container.addClass('dndupload-over');
369         },
371         /**
372          * Remove highlight on destination node
373          */
374         hide_upload_ready: function() {
375             this.container.removeClass('dndupload-over');
376         },
378         /**
379          * Tell the attached filemanager element (if any) to refresh on file
380          * upload
381          */
382         update_filemanager: function() {
383             if (this.filemanager) {
384                 // update the filemanager that we've uploaded the files
385                 this.filemanager.filepicker_callback();
386             }
387         }
388     };
390     var dnduploader = function(options) {
391         dnduploader.superclass.constructor.apply(this, arguments);
392     };
394     Y.extend(dnduploader, Y.Base, {
395         // The URL to send the upload data to.
396         api: M.cfg.wwwroot+'/repository/repository_ajax.php',
397         // Options passed into the filemanager/filepicker element.
398         options: {},
399         // The function to call when all uploads complete.
400         callback: null,
401         // The list of files dropped onto the element.
402         files: null,
403         // The ID of the 'upload' repository.
404         repositoryid: 0,
405         // Array of files already in the current folder (to check for name clashes).
406         currentfiles: null,
407         // Total number of files already uploaded (to check for exceeding limits).
408         currentfilecount: 0,
409         // The list of files to upload.
410         uploadqueue: [],
411         // This list of files with name clashes.
412         renamequeue: [],
413         // Set to true if the user has clicked on 'overwrite all'.
414         overwriteall: false,
415         // Set to true if the user has clicked on 'rename all'.
416         renameall: false,
418         /**
419          * Initialise the settings for the dnduploader
420          * @param object params - includes:
421          *                     options (copied from the filepicker / filemanager)
422          *                     repositoryid - ID of the upload repository
423          *                     callback - the function to call when uploads are complete
424          *                     currentfiles - the list of files already in the current folder in the filemanager
425          *                     currentfilecount - the total files already in the filemanager
426          *                     files - the list of files to upload
427          * @return void
428          */
429         initializer: function(params) {
430             this.options = params.options;
431             this.repositoryid = params.repositoryid;
432             this.callback = params.callback;
433             this.currentfiles = params.currentfiles;
434             this.currentfilecount = params.currentfilecount;
436             this.initialise_queue(params.files);
437         },
439         /**
440          * Entry point for starting the upload process (starts by processing any
441          * renames needed)
442          */
443         start_upload: function() {
444             this.process_renames(); // Automatically calls 'do_upload' once renames complete.
445         },
447         /**
448          * Display a message in a popup
449          * @param string msg - the message to display
450          * @param string type - 'error' or 'info'
451          */
452         print_msg: function(msg, type) {
453             var header = M.str.moodle.error;
454             if (type != 'error') {
455                 type = 'info'; // one of only two types excepted
456                 header = M.str.moodle.info;
457             }
458             if (!this.msg_dlg) {
459                 this.msg_dlg_node = Y.Node.createWithFilesSkin(M.core_filepicker.templates.message);
460                 this.msg_dlg_node.generateID();
462                 this.msg_dlg = new Y.Panel({
463                     srcNode      : this.msg_dlg_node,
464                     zIndex       : 800000,
465                     centered     : true,
466                     modal        : true,
467                     visible      : false,
468                     render       : true
469                 });
470                 this.msg_dlg.plug(Y.Plugin.Drag,{handles:['#'+this.msg_dlg_node.get('id')+' .yui3-widget-hd']});
471                 this.msg_dlg_node.one('.fp-msg-butok').on('click', function(e) {
472                     e.preventDefault();
473                     this.msg_dlg.hide();
474                 }, this);
475             }
477             this.msg_dlg.set('headerContent', header);
478             this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
479             this.msg_dlg_node.one('.fp-msg-text').setContent(msg);
480             this.msg_dlg.show();
481         },
483         /**
484          * Check the size of each file and add to either the uploadqueue or, if there
485          * is a name clash, the renamequeue
486          * @param FileList files - the files to upload
487          * @return void
488          */
489         initialise_queue: function(files) {
490             this.uploadqueue = [];
491             this.renamequeue = [];
493             // Loop through the files and find any name clashes with existing files
494             var i;
495             for (i=0; i<files.length; i++) {
496                 if (this.options.maxbytes > 0 && files[i].size > this.options.maxbytes) {
497                     // Check filesize before attempting to upload
498                     this.print_msg(M.util.get_string('uploadformlimit', 'moodle', files[i].name), 'error');
499                     this.uploadqueue = []; // No uploads if one file is too big.
500                     return;
501                 }
503                 if (this.has_name_clash(files[i].name)) {
504                     this.renamequeue.push(files[i]);
505                 } else {
506                     if (!this.add_to_upload_queue(files[i], files[i].name, false)) {
507                         return;
508                     }
509                 }
510             }
511         },
513         /**
514          * Add a single file to the uploadqueue, whilst checking the maxfiles limit
515          * @param File file - the file to add
516          * @param string filename - the name to give the file on upload
517          * @param bool overwrite - true to overwrite the existing file
518          * @return bool true if added successfully
519          */
520         add_to_upload_queue: function(file, filename, overwrite) {
521             if (!overwrite) {
522                 this.currentfilecount++;
523             }
524             if (this.options.maxfiles > 0 && this.currentfilecount > this.options.maxfiles) {
525                 // Too many files - abort entire upload.
526                 this.uploadqueue = [];
527                 this.renamequeue = [];
528                 this.print_msg(M.util.get_string('maxfilesreached', 'moodle', this.options.maxfiles), 'error');
529                 return false;
530             }
531             this.uploadqueue.push({file:file, filename:filename, overwrite:overwrite});
532             return true;
533         },
535         /**
536          * Take the next file from the renamequeue and ask the user what to do with
537          * it. Called recursively until the queue is empty, then calls do_upload.
538          * @return void
539          */
540         process_renames: function() {
541             if (this.renamequeue.length == 0) {
542                 // All rename processing complete - start the actual upload.
543                 this.do_upload();
544                 return;
545             }
546             var multiplefiles = (this.renamequeue.length > 1);
548             // Get the next file from the rename queue.
549             var file = this.renamequeue.shift();
550             // Generate a non-conflicting name for it.
551             var newname = this.generate_unique_name(file.name);
553             // If the user has clicked on overwrite/rename ALL then process
554             // this file, as appropriate, then process the rest of the queue.
555             if (this.overwriteall) {
556                 this.add_to_upload_queue(file, file.name, true);
557                 this.process_renames();
558                 return;
559             }
560             if (this.renameall) {
561                 this.add_to_upload_queue(file, newname, false);
562                 this.process_renames();
563                 return;
564             }
566             // Ask the user what to do with this file.
567             var self = this;
569             var process_dlg_node;
570             if (multiplefiles) {
571                 process_dlg_node = Y.Node.createWithFilesSkin(M.core_filepicker.templates.processexistingfilemultiple);
572             } else {
573                 process_dlg_node = Y.Node.createWithFilesSkin(M.core_filepicker.templates.processexistingfile);
574             }
575             var node = process_dlg_node;
576             node.generateID();
577             var process_dlg = new Y.Panel({
578                 srcNode      : node,
579                 headerContent: M.str.repository.fileexistsdialogheader,
580                 zIndex       : 800000,
581                 centered     : true,
582                 modal        : true,
583                 visible      : false,
584                 render       : true,
585                 buttons      : {}
586             });
587             process_dlg.plug(Y.Plugin.Drag,{handles:['#'+node.get('id')+' .yui3-widget-hd']});
589             // Overwrite original.
590             node.one('.fp-dlg-butoverwrite').on('click', function(e) {
591                 e.preventDefault();
592                 process_dlg.hide();
593                 self.add_to_upload_queue(file, file.name, true);
594                 self.process_renames();
595             }, this);
597             // Rename uploaded file.
598             node.one('.fp-dlg-butrename').on('click', function(e) {
599                 e.preventDefault();
600                 process_dlg.hide();
601                 self.add_to_upload_queue(file, newname, false);
602                 self.process_renames();
603             }, this);
605             // Cancel all uploads.
606             node.one('.fp-dlg-butcancel').on('click', function(e) {
607                 e.preventDefault();
608                 process_dlg.hide();
609             }, this);
611             // When we are at the file limit, only allow 'overwrite', not rename.
612             if (this.currentfilecount == this.options.maxfiles) {
613                 node.one('.fp-dlg-butrename').setStyle('display', 'none');
614                 if (multiplefiles) {
615                     node.one('.fp-dlg-butrenameall').setStyle('display', 'none');
616                 }
617             }
619             // If there are more files still to go, offer the 'overwrite/rename all' options.
620             if (multiplefiles) {
621                 // Overwrite all original files.
622                 node.one('.fp-dlg-butoverwriteall').on('click', function(e) {
623                     e.preventDefault();
624                     process_dlg.hide();
625                     this.overwriteall = true;
626                     self.add_to_upload_queue(file, file.name, true);
627                     self.process_renames();
628                 }, this);
630                 // Rename all new files.
631                 node.one('.fp-dlg-butrenameall').on('click', function(e) {
632                     e.preventDefault();
633                     process_dlg.hide();
634                     this.renameall = true;
635                     self.add_to_upload_queue(file, newname, false);
636                     self.process_renames();
637                 }, this);
638             }
639             node.one('.fp-dlg-text').setContent(M.util.get_string('fileexists', 'moodle', file.name));
640             process_dlg_node.one('.fp-dlg-butrename').setContent(M.util.get_string('renameto', 'repository', newname));
642             // Destroy the dialog once it has been hidden.
643             process_dlg.after('visibleChange', function(e) {
644                 if (!process_dlg.get('visible')) {
645                     process_dlg.destroy(true);
646                 }
647             });
649             process_dlg.show();
650         },
652         /**
653          * Checks if there is already a file with the given name in the current folder
654          * or in the list of already uploading files
655          * @param string filename - the name to test
656          * @return bool true if the name already exists
657          */
658         has_name_clash: function(filename) {
659             // Check against the already uploaded files
660             var i;
661             for (i=0; i<this.currentfiles.length; i++) {
662                 if (filename == this.currentfiles[i].filename) {
663                     return true;
664                 }
665             }
666             // Check against the uploading files that have already been processed
667             for (i=0; i<this.uploadqueue.length; i++) {
668                 if (filename == this.uploadqueue[i].filename) {
669                     return true;
670                 }
671             }
672             return false;
673         },
675         /**
676          * Adds _NUMBER to the end of the filename and increments this number until
677          * a unique name is found
678          * @param string filename
679          * @return string the unique filename generated
680          */
681         generate_unique_name: function(filename) {
682             // Split the filename into the basename + extension.
683             var extension;
684             var basename;
685             var dotpos = filename.lastIndexOf('.');
686             if (dotpos == -1) {
687                 basename = filename;
688                 extension = '';
689             } else {
690                 basename = filename.substr(0, dotpos);
691                 extension = filename.substr(dotpos, filename.length);
692             }
694             // Look to see if the name already has _NN at the end of it.
695             var number = 0;
696             var hasnumber = basename.match(/^(.*)_(\d+)$/);
697             if (hasnumber != null) {
698                 // Note the current number & remove it from the basename.
699                 number = parseInt(hasnumber[2]);
700                 basename = hasnumber[1];
701             }
703             // Loop through increating numbers until a unique name is found.
704             var newname;
705             do {
706                 number++;
707                 newname = basename + '_' + number + extension;
708             } while (this.has_name_clash(newname));
710             return newname;
711         },
713         /**
714          * Upload the next file from the uploadqueue - called recursively after each
715          * upload is complete, then handles the callback to the filemanager/filepicker
716          * @param lastresult - the last result from the server
717          */
718         do_upload: function(lastresult) {
719             if (this.uploadqueue.length > 0) {
720                 var filedetails = this.uploadqueue.shift();
721                 this.upload_file(filedetails.file, filedetails.filename, filedetails.overwrite);
722             } else {
723                 this.uploadfinished(lastresult);
724             }
725         },
727         /**
728          * Run the callback to the filemanager/filepicker
729          */
730         uploadfinished: function(lastresult) {
731             this.callback(lastresult);
732         },
734         /**
735          * Upload a single file via an AJAX call to the 'upload' repository. Automatically
736          * calls do_upload as each upload completes.
737          * @param File file - the file to upload
738          * @param string filename - the name to give the file
739          * @param bool overwrite - true if the existing file should be overwritten
740          */
741         upload_file: function(file, filename, overwrite) {
743             // This would be an ideal place to use the Y.io function
744             // however, this does not support data encoded using the
745             // FormData object, which is needed to transfer data from
746             // the DataTransfer object into an XMLHTTPRequest
747             // This can be converted when the YUI issue has been integrated:
748             // http://yuilibrary.com/projects/yui3/ticket/2531274
749             var xhr = new XMLHttpRequest();
750             var self = this;
751             xhr.onreadystatechange = function() { // Process the server response
752                 if (xhr.readyState == 4) {
753                     if (xhr.status == 200) {
754                         var result = JSON.parse(xhr.responseText);
755                         if (result) {
756                             if (result.error) {
757                                 self.print_msg(result.error, 'error'); // TODO add filename?
758                                 self.uploadfinished();
759                             } else {
760                                 // Only update the filepicker if there were no errors
761                                 if (result.event == 'fileexists') {
762                                     // Do not worry about this, as we only care about the last
763                                     // file uploaded, with the filepicker
764                                     result.file = result.newfile.filename;
765                                     result.url = result.newfile.url;
766                                 }
767                                 result.client_id = self.options.clientid;
768                             }
769                         }
770                         self.do_upload(result); // continue uploading
771                     } else {
772                         self.print_msg(M.util.get_string('serverconnection', 'error'), 'error');
773                         self.uploadfinished();
774                     }
775                 }
776             };
778             // Prepare the data to send
779             var formdata = new FormData();
780             formdata.append('action', 'upload');
781             formdata.append('repo_upload_file', file); // The FormData class allows us to attach a file
782             formdata.append('sesskey', M.cfg.sesskey);
783             formdata.append('repo_id', this.repositoryid);
784             formdata.append('itemid', this.options.itemid);
785             if (this.options.author) {
786                 formdata.append('author', this.options.author);
787             }
788             if (this.options.filemanager) { // Filepickers do not have folders
789                 formdata.append('savepath', this.options.filemanager.currentpath);
790             }
791             formdata.append('title', filename);
792             if (overwrite) {
793                 formdata.append('overwrite', 1);
794             }
796             // Accepted types can be either a string or an array, but an array is
797             // expected in the processing script, so make sure we are sending an array
798             if (this.options.acceptedtypes.constructor == Array) {
799                 for (var i=0; i<this.options.acceptedtypes.length; i++) {
800                     formdata.append('accepted_types[]', this.options.acceptedtypes[i]);
801                 }
802             } else {
803                 formdata.append('accepted_types[]', this.options.acceptedtypes);
804             }
806             // Send the file & required details
807             xhr.open("POST", this.api, true);
808             xhr.send(formdata);
809             return true;
810         }
811     });
813     dnduploadhelper.init(Y, options);
814 };