Merge branch 'MDL-73483-master' of https://github.com/dmitriim/moodle
[moodle.git] / lib / form / filemanager.js
blob8f9e5cba4d7333a9aa13edc3a178f08bf655bfdd
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.
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/>.
15 /**
16  *
17  * File Manager UI
18  * =====
19  * this.api, stores the URL to make ajax request
20  * this.currentpath
21  * this.filepicker_options
22  * this.movefile_dialog
23  * this.mkdir_dialog
24  * this.rename_dialog
25  * this.client_id
26  * this.filecount, how many files in this filemanager
27  * this.maxfiles
28  * this.maxbytes
29  * this.areamaxbytes, the maximum size of the area
30  * this.filemanager, contains reference to filemanager Node
31  * this.selectnode, contains referenct to select-file Node
32  * this.selectui, M.core.dialogue to select the file
33  * this.viewmode, contains current view mode - icons, tree or details
34  *
35  * FileManager options:
36  * =====
37  * this.options.currentpath
38  * this.options.itemid
39  */
41 /* eslint camelcase: off */
43 M.form_filemanager = {
44     templates: {},
45     formChangeChecker: null,
48 require(['core_form/changechecker'], function(FormChangeChecker) {
49     // This is a nasty, hacky, way of doing it but it's the smallest evil.
50     M.form_filemanager.formChangeChecker = FormChangeChecker;
51 });
53 M.form_filemanager.set_templates = function(Y, templates) {
54     M.form_filemanager.templates = templates;
57 /**
58  * This fucntion is called for each file picker on page.
59  */
60 M.form_filemanager.init = function(Y, options) {
61     var FileManagerHelper = function(options) {
62         FileManagerHelper.superclass.constructor.apply(this, arguments);
63     };
64     FileManagerHelper.NAME = "FileManager";
65     FileManagerHelper.ATTRS = {
66         options: {},
67         lang: {}
68     };
70     Y.extend(FileManagerHelper, Y.Base, {
71         api: M.cfg.wwwroot+'/repository/draftfiles_ajax.php',
72         menus: {},
73         initializer: function(options) {
74             this.options = options;
75             if (options.mainfile) {
76                 this.enablemainfile = options.mainfile;
77             }
78             this.client_id = options.client_id;
79             this.currentpath = '/';
80             this.maxfiles = options.maxfiles;
81             this.maxbytes = options.maxbytes;
82             this.areamaxbytes = options.areamaxbytes;
83             this.userprefs = options.userprefs;
84             this.emptycallback = null; // Used by drag and drop upload
86             this.filepicker_options = options.filepicker?options.filepicker:{};
87             this.filepicker_options.client_id = this.client_id;
88             this.filepicker_options.context = options.context;
89             this.filepicker_options.maxfiles = this.maxfiles;
90             this.filepicker_options.maxbytes = this.maxbytes;
91             this.filepicker_options.areamaxbytes = this.areamaxbytes;
92             this.filepicker_options.env = 'filemanager';
93             this.filepicker_options.itemid = options.itemid;
95             if (options.filecount) {
96                 this.filecount = options.filecount;
97             } else {
98                 this.filecount = 0;
99             }
100             // prepare filemanager for drag-and-drop upload
101             this.filemanager = Y.one('#filemanager-'+options.client_id);
102             if (this.filemanager.hasClass('filemanager-container') || !this.filemanager.one('.filemanager-container')) {
103                 this.dndcontainer = this.filemanager;
104             } else  {
105                 this.dndcontainer = this.filemanager.one('.filemanager-container');
106                 if (!this.dndcontainer.get('id')) {
107                     this.dndcontainer.generateID();
108                 }
109             }
110             // save template for one path element and location of path bar
111             if (this.filemanager.one('.fp-path-folder')) {
112                 this.pathnode = this.filemanager.one('.fp-path-folder');
113                 this.pathbar = this.pathnode.get('parentNode');
114                 this.pathbar.removeChild(this.pathnode);
115             }
116             // initialize 'select file' panel
117             this.selectnode = Y.Node.create(M.form_filemanager.templates.fileselectlayout);
118             this.selectnode.setAttribute('aria-live', 'assertive');
119             this.selectnode.setAttribute('role', 'dialog');
120             this.selectnode.generateID();
122             var labelid = 'fm-dialog-label_'+ this.selectnode.get('id');
123             this.selectui = new M.core.dialogue({
124                 draggable    : true,
125                 headerContent: '<h3 id="' + labelid +'">' + M.util.get_string('edit', 'moodle') + '</h3>',
126                 bodyContent  : this.selectnode,
127                 centered     : true,
128                 width        : '480px',
129                 modal        : true,
130                 visible      : false
131             });
132             Y.one('#'+this.selectnode.get('id')).setAttribute('aria-labelledby', labelid);
133             this.selectui.hide();
134             this.setup_select_file();
135             // setup buttons onclick events
136             this.setup_buttons();
137             // set event handler for lazy loading of thumbnails
138             this.filemanager.one('.fp-content').on(['scroll','resize'], this.content_scrolled, this);
139             // display files
140             this.viewmode = this.get_preference("recentviewmode");
141             if (this.viewmode != 2 && this.viewmode != 3) {
142                 this.viewmode = 1;
143             }
144             var viewmodeselectors = {'1': '.fp-vb-icons', '2': '.fp-vb-tree', '3': '.fp-vb-details'};
145             this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked');
146             this.filemanager.all(viewmodeselectors[this.viewmode]).addClass('checked');
147             this.refresh(this.currentpath); // MDL-31113 get latest list from server
148         },
150         wait: function() {
151            this.filemanager.addClass('fm-updating');
152         },
153         request: function(args, redraw) {
154             var api = this.api + '?action='+args.action;
155             var params = {};
156             var scope = this;
157             if (args['scope']) {
158                 scope = args['scope'];
159             }
160             params['sesskey'] = M.cfg.sesskey;
161             params['client_id'] = this.client_id;
162             params['filepath'] = this.currentpath;
163             params['itemid'] = this.options.itemid?this.options.itemid:0;
164             if (args['params']) {
165                 for (i in args['params']) {
166                     params[i] = args['params'][i];
167                 }
168             }
169             var cfg = {
170                 method: 'POST',
171                 on: {
172                     complete: function(id,o,p) {
173                         if (!o) {
174                             alert('IO FATAL');
175                             return;
176                         }
177                         var data = null;
178                         try {
179                             data = Y.JSON.parse(o.responseText);
180                         } catch(e) {
181                             scope.print_msg(M.util.get_string('invalidjson', 'repository'), 'error');
182                             Y.error(M.util.get_string('invalidjson', 'repository')+":\n"+o.responseText);
183                             return;
184                         }
185                         if (data && data.tree && scope.set_current_tree) {
186                             scope.set_current_tree(data.tree);
187                         }
188                         args.callback(id,data,p);
189                     }
190                 },
191                 arguments: {
192                     scope: scope
193                 },
194                 headers: {
195                     'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
196                 },
197                 data: build_querystring(params)
198             };
199             if (args.form) {
200                 cfg.form = args.form;
201             }
202             Y.io(api, cfg);
203             if (redraw) {
204                 this.wait();
205             }
206         },
207         filepicker_callback: function(obj) {
208             this.filecount++;
209             this.check_buttons();
210             this.refresh(this.currentpath);
211             M.form_filemanager.formChangeChecker.markFormChangedFromNode(this.filemanager.getDOMNode());
213             require(['core_form/events'], function(FormEvent) {
214                 FormEvent.notifyUploadChanged(this.filemanager.get('id'));
215             }.bind(this));
216         },
217         check_buttons: function() {
218             if (this.filecount>0) {
219                 this.filemanager.removeClass('fm-nofiles');
220             } else {
221                 this.filemanager.addClass('fm-nofiles');
222             }
223             if (this.filecount >= this.maxfiles && this.maxfiles!=-1) {
224                 this.filemanager.addClass('fm-maxfiles');
225             }
226             else {
227                 this.filemanager.removeClass('fm-maxfiles');
228             }
229         },
230         refresh: function(filepath, action) {
231             var scope = this;
232             this.currentpath = filepath;
233             if (!filepath) {
234                 filepath = this.currentpath;
235             } else {
236                 this.currentpath = filepath;
237             }
238             this.request({
239                 action: 'list',
240                 scope: scope,
241                 params: {'filepath':filepath},
242                 callback: function(id, obj, args) {
243                     scope.filecount = obj.filecount;
244                     scope.options = obj;
245                     scope.lazyloading = {};
246                     scope.check_buttons();
247                     scope.render(obj, action);
248                 }
249             }, true);
250         },
251         /** displays message in a popup */
252         print_msg: function(msg, type, options) {
253             var header = M.util.get_string('error', 'moodle');
254             if (type != 'error') {
255                 type = 'info'; // one of only two types excepted
256                 header = M.util.get_string('info', 'moodle');
257             }
258             if (!this.msg_dlg) {
259                 this.msg_dlg_node = Y.Node.create(M.form_filemanager.templates.message);
260                 var nodeid = this.msg_dlg_node.generateID();
261                 var previousActiveElement = null;
262                 if (typeof options.previousActiveElement != 'undefined') {
263                     previousActiveElement = options.previousActiveElement;
264                 }
265                 this.msg_dlg = new M.core.dialogue({
266                     draggable    : true,
267                     bodyContent  : this.msg_dlg_node,
268                     centered     : true,
269                     modal        : true,
270                     visible      : false,
271                     focusAfterHide: previousActiveElement,
272                 });
273                 this.msg_dlg_node.one('.fp-msg-butok').on('click', function(e) {
274                     e.preventDefault();
275                     this.msg_dlg.hide();
276                 }, this);
277             }
279             this.msg_dlg.set('headerContent', header);
280             this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
281             this.msg_dlg_node.one('.fp-msg-text').setContent(Y.Escape.html(msg));
282             this.msg_dlg.show();
283         },
284         is_disabled: function() {
285             return this.filemanager.ancestor('.fitem.disabled') != null;
286         },
287         getSelectedFiles: function() {
288             var markedFiles = this.filemanager.all('[data-togglegroup][data-toggle=slave]:checked');
289             var filenames = [];
290             markedFiles.each(function(item) {
291                 var fileinfo = this.options.list.find(function(element) {
292                     return item.getData().fullname == element.fullname;
293                 });
294                 if (fileinfo && fileinfo != undefined) {
295                     filenames.push({
296                         filepath: fileinfo.filepath,
297                         filename: fileinfo.filename
298                     });
299                 }
300             }, this);
302             return filenames;
303         },
304         setup_buttons: function() {
305             var button_download = this.filemanager.one('.fp-btn-download');
306             var button_create   = this.filemanager.one('.fp-btn-mkdir');
307             var button_addfile  = this.filemanager.one('.fp-btn-add');
308             var buttonDeleteFile = this.filemanager.one('.fp-btn-delete');
310             // setup 'add file' button
311             button_addfile.on('click', this.show_filepicker, this);
313             var dndarrow = this.filemanager.one('.dndupload-arrow');
314             if (dndarrow) {
315                 dndarrow.on('click', this.show_filepicker, this);
316             }
318             // setup 'make a folder' button
319             if (this.options.subdirs) {
320                 button_create.on('click',function(e) {
321                     e.preventDefault();
322                     if (this.is_disabled()) {
323                         return;
324                     }
325                     var scope = this;
326                     // a function used to perform an ajax request
327                     var perform_action = function(e) {
328                         e.preventDefault();
329                         var foldername = Y.one('#fm-newname-'+scope.client_id).get('value');
330                         if (!foldername) {
331                             scope.mkdir_dialog.hide();
332                             return;
333                         }
334                         scope.request({
335                             action:'mkdir',
336                             params: {filepath:scope.currentpath, newdirname:foldername},
337                             callback: function(id, obj, args) {
338                                 var filepath = obj.filepath;
339                                 scope.mkdir_dialog.hide();
340                                 scope.refresh(filepath);
341                                 Y.one('#fm-newname-'+scope.client_id).set('value', '');
342                                 M.form_filemanager.formChangeChecker.markFormChangedFromNode(scope.filemanager.getDOMNode());
343                             }
344                         });
345                     };
346                     var validate_folder_name = function() {
347                         var valid = false;
348                         var foldername = Y.one('#fm-newname-'+scope.client_id).get('value');
349                         if (foldername.length > 0) {
350                             valid = true;
351                         }
352                         var btn = Y.one('#fm-mkdir-butcreate-'+scope.client_id);
353                         if (btn) {
354                             btn.set('disabled', !valid);
355                         }
356                         return valid;
357                     };
358                     if (!this.mkdir_dialog) {
359                         var node = Y.Node.create(M.form_filemanager.templates.mkdir);
360                         this.mkdir_dialog = new M.core.dialogue({
361                             draggable    : true,
362                             bodyContent  : node,
363                             centered     : true,
364                             modal        : true,
365                             visible      : false,
366                             focusAfterHide: e.target.ancestor('a', true),
367                         });
368                         node.one('.fp-dlg-butcreate').set('id', 'fm-mkdir-butcreate-'+this.client_id).on('click',
369                                 perform_action, this);
370                         node.one('input').set('id', 'fm-newname-'+this.client_id).on('keydown', function(e) {
371                             var valid = Y.bind(validate_folder_name, this)();
372                             if (valid && e.keyCode === 13) {
373                                 Y.bind(perform_action, this)(e);
374                             }
375                         }, this);
376                         node.one('#fm-newname-'+this.client_id).on(['keyup', 'change'], function(e) {
377                             Y.bind(validate_folder_name, this)();
378                         }, this);
380                         node.one('label').set('for', 'fm-newname-' + this.client_id);
381                         node.all('.fp-dlg-butcancel').on('click', function(e){e.preventDefault();this.mkdir_dialog.hide();}, this);
382                         node.all('.fp-dlg-curpath').set('id', 'fm-curpath-'+this.client_id);
383                     }
384                     this.mkdir_dialog.show();
386                     // Default folder name:
387                     var foldername = M.util.get_string('newfolder', 'repository');
388                     while (this.has_folder(foldername)) {
389                         foldername = increment_filename(foldername, true);
390                     }
391                     Y.one('#fm-newname-'+scope.client_id).set('value', foldername);
392                     Y.bind(validate_folder_name, this)();
393                     Y.one('#fm-newname-'+scope.client_id).focus().select();
394                     Y.all('#fm-curpath-'+scope.client_id).setContent(this.currentpath);
395                 }, this);
396             } else {
397                 this.filemanager.addClass('fm-nomkdir');
398             }
400             // setup 'download this folder' button
401             button_download.on('click',function(e) {
402                 e.preventDefault();
403                 if (this.is_disabled()) {
404                     return;
405                 }
406                 var scope = this;
408                 var image_downloading = this.filemanager.one('.fp-img-downloading');
409                 if (image_downloading.getStyle('display') == 'inline') {
410                     return;
411                 }
412                 image_downloading.setStyle('display', 'inline');
413                 var filenames = this.getSelectedFiles();
415                 // perform downloaddir ajax request
416                 this.request({
417                     action: 'downloadselected',
418                     scope: scope,
419                     params: {selected: Y.JSON.stringify(filenames)},
420                     callback: function(id, obj, args) {
421                         var image_downloading = scope.filemanager.one('.fp-img-downloading');
422                         image_downloading.setStyle('display', 'none');
424                         if (obj) {
425                             scope.refresh(obj.filepath);
426                             node = Y.Node.create('<iframe></iframe>').setStyles({
427                                 visibility : 'hidden',
428                                 width : '1px',
429                                 height : '1px'
430                             });
431                             node.set('src', obj.fileurl);
432                             Y.one('body').appendChild(node);
433                         } else {
434                             scope.print_msg(M.util.get_string('draftareanofiles', 'repository'), 'error');
435                         }
436                     }
437                 });
438             }, this);
440             buttonDeleteFile.on('click', function(e) {
441                 e.preventDefault();
442                 var dialogOptions = {};
443                 var filenames = this.getSelectedFiles();
444                 var previousActiveElement = e.target.ancestor('a', true);
446                 if (!filenames.length) {
447                     var options = {};
448                     options.previousActiveElement = previousActiveElement;
449                     this.print_msg(M.util.get_string('nofilesselected', 'repository'), 'error', options);
450                     return;
451                 }
453                 dialogOptions.scope = this;
454                 var params = {
455                     selected: Y.JSON.stringify(filenames)
456                 };
457                 dialogOptions.header = M.util.get_string('confirm', 'moodle');
458                 dialogOptions.message = M.util.get_string('confirmdeleteselectedfile', 'repository', filenames.length);
459                 dialogOptions.previousActiveElement = previousActiveElement;
460                 dialogOptions.callbackargs = [params];
461                 dialogOptions.callback = function(params) {
462                     this.request({
463                         action: 'deleteselected',
464                         scope: this,
465                         params: params,
466                         callback: function(id, obj, args) {
467                             // Do something here
468                             args.scope.filecount -= params.length;
469                             if (obj && obj.length) {
470                                 args.scope.refresh(obj[0], {action: 'delete'});
471                             }
472                             M.form_filemanager.formChangeChecker.markFormChangedFromNode(this.scope.filemanager.getDOMNode());
474                             require(['core_form/events'], function(FormEvent) {
475                                 FormEvent.notifyUploadChanged(this.scope.filemanager.get('id'));
476                             }.bind(this));
477                         }
478                     });
479                 };
480                 this.show_confirm_dialog(dialogOptions);
481             }, this);
483             this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').
484                 on('click', function(e) {
485                     e.preventDefault();
486                     var viewbar = this.filemanager.one('.fp-viewbar')
487                     if (!this.is_disabled() && (!viewbar || !viewbar.hasClass('disabled'))) {
488                         this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked')
489                         if (e.currentTarget.hasClass('fp-vb-tree')) {
490                             this.viewmode = 2;
491                         } else if (e.currentTarget.hasClass('fp-vb-details')) {
492                             this.viewmode = 3;
493                         } else {
494                             this.viewmode = 1;
495                         }
496                         e.currentTarget.addClass('checked')
497                         this.render();
498                         this.filemanager.one('.fp-content').setAttribute('tabIndex', '0');
499                         this.filemanager.one('.fp-content').focus();
500                         this.set_preference('recentviewmode', this.viewmode);
501                     }
502                 }, this);
503         },
505         show_filepicker: function (e) {
506             // if maxfiles == -1, the no limit
507             e.preventDefault();
508             if (this.is_disabled()) {
509                 return;
510             }
511             var options = this.filepicker_options;
512             options.formcallback = this.filepicker_callback;
513             // XXX: magic here, to let filepicker use filemanager scope
514             options.magicscope = this;
515             options.savepath = this.currentpath;
516             options.previousActiveElement = e.target.ancestor('a', true);
517             M.core_filepicker.show(Y, options);
518         },
520         print_path: function() {
521             var p = this.options.path;
522             this.pathbar.setContent('').addClass('empty');
523             if (p && p.length!=0 && this.viewmode != 2) {
524                 for(var i = 0; i < p.length; i++) {
525                     var el = this.pathnode.cloneNode(true);
526                     this.pathbar.appendChild(el);
528                     if (i == 0) {
529                         el.addClass('first');
530                     }
531                     if (i == p.length-1) {
532                         el.addClass('last');
533                     }
535                     if (i%2) {
536                         el.addClass('even');
537                     } else {
538                         el.addClass('odd');
539                     }
540                     el.one('.fp-path-folder-name').setContent(Y.Escape.html(p[i].name)).
541                         on('click', function(e, path) {
542                             e.preventDefault();
543                             if (!this.is_disabled()) {
544                                 this.refresh(path);
545                             }
546                         }, this, p[i].path);
547                 }
548                 this.pathbar.removeClass('empty');
549             }
550         },
551         get_filepath: function(obj) {
552             if (obj.path && obj.path.length) {
553                 return obj.path[obj.path.length-1].path;
554             }
555             return '';
556         },
557         treeview_dynload: function(node, cb) {
558             var retrieved_children = {};
559             if (node.children) {
560                 for (var i in node.children) {
561                     retrieved_children[node.children[i].path] = node.children[i];
562                 }
563             }
564             if (!node.path || node.path == '/') {
565                 // this is a root pseudo folder
566                 node.fileinfo.filepath = '/';
567                 node.fileinfo.type = 'folder';
568                 node.fileinfo.fullname = node.fileinfo.title;
569                 node.fileinfo.filename = '.';
570             }
571             this.request({
572                 action:'list',
573                 params: {filepath:node.path?node.path:''},
574                 scope:this,
575                 callback: function(id, obj, args) {
576                     var list = obj.list;
577                     var scope = args.scope;
578                     // check that user did not leave the view mode before recieving this response
579                     if (!(scope.viewmode == 2 && node && node.getChildrenEl())) {
580                         return;
581                     }
582                     if (cb != null) { // (in manual mode do not update current path)
583                         scope.options = obj;
584                         scope.currentpath = node.path?node.path:'/';
585                     }
586                     node.highlight(false);
587                     node.origlist = obj.list ? obj.list : null;
588                     node.origpath = obj.path ? obj.path : null;
589                     node.children = [];
590                     for(k in list) {
591                         if (list[k].type == 'folder' && retrieved_children[list[k].filepath]) {
592                             // if this child is a folder and has already been retrieved
593                             retrieved_children[list[k].filepath].fileinfo = list[k];
594                             node.children[node.children.length] = retrieved_children[list[k].filepath];
595                         } else {
596                             // append new file to the list
597                             scope.view_files([list[k]]);
598                         }
599                     }
600                     if (cb == null) {
601                         node.refresh();
602                     } else {
603                         // invoke callback requested by TreeView component
604                         cb();
605                     }
606                     scope.content_scrolled();
607                 }
608             }, false);
609         },
610         content_scrolled: function(e) {
611             setTimeout(Y.bind(function() {
612                 if (this.processingimages) {return;}
613                 this.processingimages = true;
614                 var scope = this,
615                     fpcontent = this.filemanager.one('.fp-content'),
616                     fpcontenty = fpcontent.getY(),
617                     fpcontentheight = fpcontent.getStylePx('height'),
618                     is_node_visible = function(node) {
619                         var offset = node.getY()-fpcontenty;
620                         if (offset <= fpcontentheight && (offset >=0 || offset+node.getStylePx('height')>=0)) {
621                             return true;
622                         }
623                         return false;
624                     };
625                 // replace src for visible images that need to be lazy-loaded
626                 if (scope.lazyloading) {
627                     fpcontent.all('img').each( function(node) {
628                         if (node.get('id') && scope.lazyloading[node.get('id')] && is_node_visible(node)) {
629                             node.setImgRealSrc(scope.lazyloading);
630                         }
631                     });
632                 }
633                 this.processingimages = false;
634             }, this), 200)
635         },
636         view_files: function(appendfiles, actionfiles) {
637             this.filemanager.removeClass('fm-updating').removeClass('fm-noitems');
638             if ((appendfiles == null) && (!this.options.list || this.options.list.length == 0) && this.viewmode != 2) {
639                 this.filemanager.addClass('fm-noitems');
640                 // This is used to focus after refreshing the list files is empty by deletion file action.
641                 if (actionfiles !== undefined && actionfiles.action == 'delete') {
642                     this.filemanager.one('.fp-btn-add a').focus();
643                 }
644                 return;
645             }
646             var list = (appendfiles != null) ? appendfiles : this.options.list;
647             var element_template;
648             if (this.viewmode == 2 || this.viewmode == 3) {
649                 element_template = Y.Node.create(M.form_filemanager.templates.listfilename);
650             } else {
651                 this.viewmode = 1;
652                 element_template = Y.Node.create(M.form_filemanager.templates.iconfilename);
653             }
655             if (this.viewmode == 1 || this.viewmode == 2) {
656                 this.filemanager.one('.fp-btn-delete').addClass('d-none');
657             } else {
658                 this.filemanager.one('.fp-btn-delete').removeClass('d-none');
659             }
660             var options = {
661                 viewmode : this.viewmode,
662                 appendonly : appendfiles != null,
663                 filenode : element_template,
664                 disablecheckboxes: false,
665                 callbackcontext : this,
666                 callback : function(e, node) {
667                     if (e.preventDefault) { e.preventDefault(); }
668                     if (node.type == 'folder') {
669                         this.refresh(node.filepath);
670                     } else {
671                         // This is used to focus on file after dialogue closed.
672                         var previousActiveElement = e.target.ancestor('a', true);
673                         this.options.previousActiveElement = previousActiveElement;
674                         this.selectui.set('focusOnPreviousTargetAfterHide', true);
675                         this.selectui.set('focusAfterHide', previousActiveElement);
676                         this.select_file(node);
677                     }
678                 },
679                 rightclickcallback : function(e, node) {
680                     if (e.preventDefault) { e.preventDefault(); }
681                     this.select_file(node);
682                 },
683                 classnamecallback : function(node) {
684                     var classname = '';
685                     if (node.type == 'folder' || (!node.type && !node.filename)) {
686                         classname = classname + ' fp-folder';
687                     }
688                     if (node.filename || node.filepath || (node.path && node.path != '/')) {
689                         classname = classname + ' fp-hascontextmenu';
690                     }
691                     if (node.isref) {
692                         classname = classname + ' fp-isreference';
693                     }
694                     if (node.refcount) {
695                         classname = classname + ' fp-hasreferences';
696                     }
697                     if (node.originalmissing) {
698                         classname = classname + ' fp-originalmissing';
699                     }
700                     if (node.sortorder == 1) { classname = classname + ' fp-mainfile';}
701                     return Y.Lang.trim(classname);
702                 }
703             };
704             if (this.viewmode == 2) {
705                 options.dynload = true;
706                 options.filepath = this.options.path;
707                 options.treeview_dynload = this.treeview_dynload;
708                 options.norootrightclick = true;
709                 options.callback = function(e, node) {
710                     // TODO MDL-32736 e is not an event here but an object with properties 'event' and 'node'
711                     if (!node.fullname) {return;}
712                     if (node.type != 'folder') {
713                         if (e.node.parent && e.node.parent.origpath) {
714                             // set the current path
715                             this.options.path = e.node.parent.origpath;
716                             this.options.list = e.node.parent.origlist;
717                             this.print_path();
718                         }
719                         this.currentpath = node.filepath;
720                         var previousActiveElement = Y.Node(e.event.target).ancestor('a', true);
721                         this.options.previousActiveElement = previousActiveElement;
722                         this.selectui.set('focusOnPreviousTargetAfterHide', true);
723                         this.selectui.set('focusAfterHide', previousActiveElement);
724                         this.select_file(node);
725                     } else {
726                         // save current path and filelist (in case we want to jump to other viewmode)
727                         this.options.path = e.node.origpath;
728                         this.options.list = e.node.origlist;
729                         this.currentpath = node.filepath;
730                         this.print_path();
731                         //this.content_scrolled();
732                     }
733                 };
734             }
735             if (!this.lazyloading) {
736                 this.lazyloading={};
737             }
738             this.filemanager.one('.fp-content').fp_display_filelist(options, list, this.lazyloading);
739             this.content_scrolled();
740             // This is used to focus after refreshing the list files by update file and set main file action.
741             if (actionfiles !== undefined) {
742                 if (actionfiles.action == 'updatefile' || actionfiles.action == 'setmainfile') {
743                     var fileslist = this.filemanager.one('.fp-content');
744                     fileslist.all('a').each(function(parentnode) {
745                         parentnode.all('.fp-filename').each(function(childnode) {
746                             if (childnode.get('innerHTML') == actionfiles.newfilename) {
747                                 parentnode.focus();
748                             }
749                         });
750                     });
751                 }
752                 if (actionfiles.action == 'delete') {
753                     this.filemanager.one('.fp-btn-delete a').focus();
754                 }
755             }
756         },
757         populateLicensesSelect: function(licensenode, filenode) {
758             if (!licensenode) {
759                 return;
760             }
761             licensenode.setContent('');
762             var selectedlicense = this.filepicker_options.defaultlicense;
763             if (filenode) {
764                 // File has a license already, use it.
765                 selectedlicense = filenode.license;
766             } else if (this.filepicker_options.rememberuserlicensepref && this.get_preference('recentlicense')) {
767                 // When 'Remember user licence preference' is enabled use the last license selected by the user, if any.
768                 selectedlicense = this.get_preference('recentlicense');
769             }
770             var licenses = this.filepicker_options.licenses;
771             for (var i in licenses) {
772                 // Include the file's current license, even if not enabled, to prevent displaying
773                 // misleading information about which license the file currently has assigned to it.
774                 if (licenses[i].enabled == true || (filenode !== undefined && licenses[i].shortname === filenode.license)) {
775                     var option = Y.Node.create('<option/>').
776                     set('selected', (licenses[i].shortname == selectedlicense)).
777                     set('value', licenses[i].shortname).
778                     setContent(Y.Escape.html(licenses[i].fullname));
779                     licensenode.appendChild(option);
780                 }
781             }
782         },
783         set_current_tree: function(tree) {
784             var appendfilepaths = function(list, node) {
785                 if (!node || !node.children || !node.children.length) {return;}
786                 for (var i in node.children) {
787                     list[list.length] = node.children[i].filepath;
788                     appendfilepaths(list, node.children[i]);
789                 }
790             }
791             var list = ['/'];
792             appendfilepaths(list, tree);
793             var selectnode = this.selectnode;
794             node = selectnode.one('.fp-path select');
795             node.setContent('');
796             for (var i in list) {
797                 node.appendChild(Y.Node.create('<option/>').
798                     set('value', list[i]).setContent(Y.Escape.html(list[i])));
799             }
800         },
801         update_file: function(confirmed) {
802             var selectnode = this.selectnode;
803             var fileinfo = this.selectui.fileinfo;
805             var newfilename = Y.Lang.trim(selectnode.one('.fp-saveas input').get('value'));
806             var filenamechanged = (newfilename && newfilename != fileinfo.fullname);
807             var pathselect = selectnode.one('.fp-path select'),
808                     pathindex = pathselect.get('selectedIndex'),
809                     targetpath = pathselect.get("options").item(pathindex).get('value');
810             var filepathchanged = (targetpath != this.get_parent_folder_name(fileinfo));
811             var newauthor = Y.Lang.trim(selectnode.one('.fp-author input').get('value'));
812             var authorchanged = (newauthor != Y.Lang.trim(fileinfo.author));
813             var licenseselect = selectnode.one('.fp-license select'),
814                     licenseindex = licenseselect.get('selectedIndex'),
815                     newlicense = licenseselect.get("options").item(licenseindex).get('value');
816             var licensechanged = (newlicense != fileinfo.license);
818             var params, action;
819             var dialog_options = {callback:this.update_file, callbackargs:[true], scope:this};
820             if (fileinfo.type == 'folder') {
821                 if (!newfilename) {
822                     this.print_msg(M.util.get_string('entername', 'repository'), 'error');
823                     return;
824                 }
825                 if (filenamechanged || filepathchanged) {
826                     if (!confirmed) {
827                         dialog_options.message = M.util.get_string('confirmrenamefolder', 'repository');
828                         this.show_confirm_dialog(dialog_options);
829                         this.selectui.hide();
830                         return;
831                     }
832                     params = {filepath:fileinfo.filepath, newdirname:newfilename, newfilepath:targetpath};
833                     action = 'updatedir';
834                 }
835             } else {
836                 if (!newfilename) {
837                     this.print_msg(M.util.get_string('enternewname', 'repository'), 'error');
838                     return;
839                 }
841                 if ((filenamechanged || filepathchanged) && !confirmed) {
842                     var warnings = '';
843                     var originalfilenamearr = fileinfo.fullname.split('.');
844                     var originalextension = (originalfilenamearr.length > 1) ? originalfilenamearr.pop() : "";
845                     var newfilenamearr = newfilename.split('.');
846                     var newextension = (newfilenamearr.length > 1) ? newfilenamearr.pop() : "";
848                     if (newextension !== originalextension) {
849                         if (newextension === "") {
850                             var string = M.util.get_string('originalextensionremove', 'repository', originalextension);
851                         } else {
852                             var stringvars = {
853                                 originalextension: originalextension,
854                                 newextension: newextension
855                             }
856                             string = M.util.get_string('originalextensionchange', 'repository', stringvars);
857                         }
858                         warnings = warnings.concat('<li>', string, '</li>');
859                     }
860                     if (fileinfo.refcount) {
861                         var string = M.util.get_string('aliaseschange', 'repository', fileinfo.refcount);
862                         warnings = warnings.concat('<li>', string, '</li>');
863                     }
864                     if (warnings.length > 0) {
865                         var message = '';
866                         var confirmmsg = M.util.get_string('confirmrenamefile', 'repository', fileinfo.refcount);
867                         dialog_options.message = message.concat('<p>', confirmmsg, '</p>',
868                             '<ul class="px-5">', warnings, '</ul>');
869                         this.show_confirm_dialog(dialog_options);
870                         return;
871                     }
872                 }
873                 if (filenamechanged || filepathchanged || licensechanged || authorchanged) {
874                     params = {filepath:fileinfo.filepath, filename:fileinfo.fullname,
875                         newfilename:newfilename, newfilepath:targetpath,
876                         newlicense:newlicense, newauthor:newauthor};
877                     action = 'updatefile';
878                 }
879             }
880             if (!action) {
881                 // no changes
882                 this.selectui.hide();
883                 return;
884             }
885             selectnode.addClass('loading');
886             this.request({
887                 action: action,
888                 scope: this,
889                 params: params,
890                 callback: function(id, obj, args) {
891                     if (obj.error) {
892                         selectnode.removeClass('loading');
893                         args.scope.print_msg(obj.error, 'error');
894                     } else {
895                         args.scope.selectui.hide();
896                         var actionfile = {action: action, newfilename: newfilename};
897                         args.scope.refresh((obj && obj.filepath) ? obj.filepath : '/', actionfile);
898                         M.form_filemanager.formChangeChecker.markFormChangedFromNode(this.scope.filemanager.getDOMNode());
899                     }
900                 }
901             });
902         },
903         /**
904          * Displays a confirmation dialog
905          * Expected attributes in dialog_options: message, callback, callbackargs(optional), scope(optional)
906          */
907         show_confirm_dialog: function(dialogOptions) {
908             require(['core/notification', 'core/str'], function(Notification, Str) {
909                 Notification.saveCancelPromise(
910                     Str.get_string('confirm', 'moodle'),
911                     dialogOptions.message,
912                     Str.get_string('yes', 'moodle')
913                 ).then(function() {
914                     dialogOptions.callback.apply(dialogOptions.scope, dialogOptions.callbackargs);
915                     return;
916                 }).catch(function() {
917                     // User cancelled.
918                 });
919             });
920         },
921         setup_select_file: function() {
922             var selectnode = this.selectnode;
923             var scope = this;
924             // bind labels with corresponding inputs
925             selectnode.all('.fp-saveas,.fp-path,.fp-author,.fp-license').each(function (node) {
926                 node.all('label').set('for', node.one('input,select').generateID());
927             });
928             // register event on clicking buttons
929             selectnode.one('.fp-file-update').on('click', function(e) {
930                 e.preventDefault();
931                 this.update_file();
932             }, this);
933             selectnode.all('form input').on('key', function(e) {
934                 e.preventDefault();
935                 scope.update_file();
936             }, 'enter');
937             selectnode.one('.fp-file-download').on('click', function(e) {
938                 e.preventDefault();
939                 if (this.selectui.fileinfo.type != 'folder') {
940                     node = Y.Node.create('<iframe></iframe>').setStyles({
941                         visibility : 'hidden',
942                         width : '1px',
943                         height : '1px'
944                     });
945                     node.set('src', this.selectui.fileinfo.url);
946                     Y.one('body').appendChild(node);
947                 }
948             }, this);
949             selectnode.one('.fp-file-delete').on('click', function(e) {
950                 e.preventDefault();
951                 var dialog_options = {
952                     scope: this,
953                     header: M.util.get_string('confirm', 'moodle'),
954                 };
955                 var params = {};
956                 var fileinfo = this.selectui.fileinfo;
957                 params.filepath = fileinfo.filepath;
958                 if (fileinfo.type == 'folder') {
959                     params.filename = '.';
960                     dialog_options.message = M.util.get_string('confirmdeletefolder', 'repository');
961                 } else {
962                     params.filename = fileinfo.fullname;
963                     if (fileinfo.refcount) {
964                         dialog_options.message = M.util.get_string('confirmdeletefilewithhref', 'repository', fileinfo.refcount);
965                     } else {
966                         dialog_options.message = M.util.get_string('confirmdeletefile', 'repository');
967                     }
968                 }
969                 dialog_options.callbackargs = [params];
970                 dialog_options.callback = function(params) {
971                     //selectnode.addClass('loading');
972                     this.request({
973                         action: 'delete',
974                         scope: this,
975                         params: params,
976                         callback: function(id, obj, args) {
977                             //args.scope.selectui.hide();
978                             args.scope.filecount--;
979                             args.scope.refresh(obj.filepath, {action: 'delete'});
980                             M.form_filemanager.formChangeChecker.markFormChangedFromNode(this.scope.filemanager.getDOMNode());
982                             require(['core_form/events'], function(FormEvent) {
983                                 FormEvent.notifyUploadChanged(this.scope.filemanager.get('id'));
984                             }.bind(this));
985                         }
986                     });
987                 };
988                 this.selectui.hide(); // TODO remove this after confirm dialog is replaced with YUI3
989                 // This is used to focus on before active element after confirm dialogue closed.
990                 if (this.options.previousActiveElement !== undefined) {
991                     dialog_options.previousActiveElement = this.options.previousActiveElement;
992                 }
993                 this.show_confirm_dialog(dialog_options);
994             }, this);
995             selectnode.one('.fp-file-zip').on('click', function(e) {
996                 e.preventDefault();
997                 var params = {};
998                 var fileinfo = this.selectui.fileinfo;
999                 if (fileinfo.type != 'folder') {
1000                     // this button should not even be shown
1001                     return;
1002                 }
1003                 params['filepath']   = fileinfo.filepath;
1004                 params['filename']   = '.';
1005                 selectnode.addClass('loading');
1006                 this.request({
1007                     action: 'zip',
1008                     scope: this,
1009                     params: params,
1010                     callback: function(id, obj, args) {
1011                         args.scope.selectui.hide();
1012                         args.scope.refresh(obj.filepath);
1013                     }
1014                 });
1015             }, this);
1016             selectnode.one('.fp-file-unzip').on('click', function(e) {
1017                 e.preventDefault();
1018                 var params = {};
1019                 var fileinfo = this.selectui.fileinfo;
1020                 if (fileinfo.type != 'zip') {
1021                     // this button should not even be shown
1022                     return;
1023                 }
1024                 params['filepath'] = fileinfo.filepath;
1025                 params['filename'] = fileinfo.fullname;
1026                 // The unlimited value of areamaxbytes is -1, it is defined by FILE_AREA_MAX_BYTES_UNLIMITED.
1027                 params['areamaxbytes'] = this.areamaxbytes ? this.areamaxbytes : -1;
1028                 selectnode.addClass('loading');
1029                 this.request({
1030                     action: 'unzip',
1031                     scope: this,
1032                     params: params,
1033                     callback: function(id, obj, args) {
1034                         if (obj.error) {
1035                             selectnode.removeClass('loading');
1036                             args.scope.print_msg(obj.error, 'error', options);
1037                         } else {
1038                             args.scope.selectui.hide();
1039                             args.scope.refresh(obj.filepath);
1040                         }
1041                     }
1042                 });
1043             }, this);
1044             selectnode.one('.fp-file-setmain').on('click', function(e) {
1045                 e.preventDefault();
1046                 var params = {};
1047                 var fileinfo = this.selectui.fileinfo;
1048                 if (!this.enablemainfile || fileinfo.type == 'folder') {
1049                     // this button should not even be shown for folders or when mainfile is disabled
1050                     return;
1051                 }
1052                 params['filepath'] = fileinfo.filepath;
1053                 params['filename'] = fileinfo.fullname;
1054                 selectnode.addClass('loading');
1055                 this.request({
1056                     action: 'setmainfile',
1057                     scope: this,
1058                     params: params,
1059                     callback: function(id, obj, args) {
1060                         args.scope.selectui.hide();
1061                         var actionfile = {action: 'setmainfile', newfilename: fileinfo.fullname};
1062                         args.scope.refresh(fileinfo.filepath, actionfile);
1063                     }
1064                 });
1065             }, this);
1066             selectnode.all('.fp-file-cancel').on('click', function(e) {
1067                 e.preventDefault();
1068                 // TODO if changed asked to confirm, the same with close button
1069                 this.selectui.hide();
1070             }, this);
1071             selectnode.all('.fp-file-update, .fp-file-download, .fp-file-delete, .fp-file-zip, .fp-file-unzip, ' +
1072                 '.fp-file-setmain, .fp-file-cancel').on('key', function(e) {
1073                     e.preventDefault();
1074                     this.simulate('click');
1075             }, 'enter');
1076         },
1077         get_parent_folder_name: function(node) {
1078             if (node.type != 'folder' || node.filepath.length < node.fullname.length+1) {
1079                 return node.filepath;
1080             }
1081             var basedir = node.filepath.substr(0, node.filepath.length - node.fullname.length - 1);
1082             var lastdir = node.filepath.substr(node.filepath.length - node.fullname.length - 2);
1083             if (lastdir == '/' + node.fullname + '/') {
1084                 return basedir;
1085             }
1086             return node.filepath;
1087         },
1088         select_file: function(node) {
1089             if (this.is_disabled()) {
1090                 return;
1091             }
1092             var selectnode = this.selectnode;
1093             selectnode.removeClass('loading').removeClass('fp-folder').
1094                 removeClass('fp-file').removeClass('fp-zip').removeClass('fp-cansetmain');
1095             if (node.type == 'folder' || node.type == 'zip') {
1096                 selectnode.addClass('fp-'+node.type);
1097             } else {
1098                 selectnode.addClass('fp-file');
1099             }
1100             if (this.enablemainfile && (node.sortorder != 1) && node.type == 'file') {
1101                 selectnode.addClass('fp-cansetmain');
1102             }
1103             this.selectui.fileinfo = node;
1104             selectnode.one('.fp-saveas input').set('value', node.fullname);
1105             var foldername = this.get_parent_folder_name(node);
1106             selectnode.all('.fp-author input').set('value', node.author ? node.author : '');
1107             this.populateLicensesSelect(selectnode.one('.fp-license select'), node);
1108             selectnode.all('.fp-path select option[selected]').set('selected', false);
1109             selectnode.all('.fp-path select option').each(function(el){
1110                 if (el.get('value') == foldername) {
1111                     el.set('selected', true);
1112                 }
1113             });
1114             selectnode.all('.fp-author input, .fp-license select').set('disabled',(node.type == 'folder')?'disabled':'');
1115             // display static information about a file (when known)
1116             var attrs = ['datemodified','datecreated','size','dimensions','original','reflist'];
1117             for (var i in attrs) {
1118                 if (selectnode.one('.fp-'+attrs[i])) {
1119                     var value = (node[attrs[i]+'_f']) ? node[attrs[i]+'_f'] : (node[attrs[i]] ? node[attrs[i]] : '');
1120                     // Escape if the attribute being evaluated is not for the list of reference files.
1121                     if (attrs[i] !== 'reflist') {
1122                         value = Y.Escape.html(value);
1123                     }
1124                     selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '')
1125                         .one('.fp-value').setContent(value);
1126                 }
1127             }
1128             // display thumbnail
1129             var imgnode = Y.Node.create('<img/>').
1130                 set('src', node.realthumbnail ? node.realthumbnail : node.thumbnail).
1131                 setStyle('maxHeight', ''+(node.thumbnail_height ? node.thumbnail_height : 90)+'px').
1132                 setStyle('maxWidth', ''+(node.thumbnail_width ? node.thumbnail_width : 90)+'px');
1133             selectnode.one('.fp-thumbnail').setContent('').appendChild(imgnode);
1134             // load original location if applicable
1135             if (node.isref && !node.original) {
1136                 selectnode.one('.fp-original').removeClass('fp-unknown').addClass('fp-loading');
1137                 this.request({
1138                     action: 'getoriginal',
1139                     scope: this,
1140                     params: {'filepath':node.filepath,'filename':node.fullname},
1141                     callback: function(id, obj, args) {
1142                         // check if we did not select another file meanwhile
1143                         var scope = args.scope;
1144                         if (scope.selectui.fileinfo && node &&
1145                                 scope.selectui.fileinfo.filepath == node.filepath &&
1146                                 scope.selectui.fileinfo.fullname == node.fullname) {
1147                             selectnode.one('.fp-original').removeClass('fp-loading');
1148                             if (obj.original) {
1149                                 node.original = obj.original;
1150                                 selectnode.one('.fp-original .fp-value').setContent(Y.Escape.html(node.original));
1151                             } else {
1152                                 selectnode.one('.fp-original .fp-value').setContent(M.util.get_string('unknownsource', 'repository'));
1153                             }
1154                         }
1155                     }
1156                 }, false);
1157             }
1158             // load references list if applicable
1159             selectnode.one('.fp-refcount').setContent(node.refcount ? M.util.get_string('referencesexist', 'repository', node.refcount) : '');
1160             if (node.refcount && !node.reflist) {
1161                 selectnode.one('.fp-reflist').removeClass('fp-unknown').addClass('fp-loading');
1162                 this.request({
1163                     action: 'getreferences',
1164                     scope: this,
1165                     params: {'filepath':node.filepath,'filename':node.fullname},
1166                     callback: function(id, obj, args) {
1167                         // check if we did not select another file meanwhile
1168                         var scope = args.scope;
1169                         if (scope.selectui.fileinfo && node &&
1170                                 scope.selectui.fileinfo.filepath == node.filepath &&
1171                                 scope.selectui.fileinfo.fullname == node.fullname) {
1172                             selectnode.one('.fp-reflist').removeClass('fp-loading');
1173                             if (obj.references) {
1174                                 node.reflist = '';
1175                                 for (var i in obj.references) {
1176                                     node.reflist += '<li>'+Y.Escape.html(obj.references[i])+'</li>';
1177                                 }
1178                                 selectnode.one('.fp-reflist .fp-value').setContent(node.reflist);
1179                             } else {
1180                                 selectnode.one('.fp-reflist .fp-value').setContent('');
1181                             }
1182                         }
1183                     }
1184                 }, false);
1185             }
1186             // update dialog header
1187             var nodename = node.fullname;
1188             // Limit the string length so it fits nicely on mobile devices
1189             var namelength = 50;
1190             if (nodename.length > namelength) {
1191                 nodename = nodename.substring(0, namelength) + '...';
1192             }
1193             Y.one('#fm-dialog-label_'+selectnode.get('id')).setContent(Y.Escape.html(M.util.get_string('edit', 'moodle')+' '+nodename));
1194             // show panel
1195             this.selectui.show();
1196             Y.one('#'+selectnode.get('id')).focus();
1197         },
1198         render: function(obj, action) {
1199             this.print_path();
1200             this.view_files(null, action);
1201         },
1202         has_folder: function(foldername) {
1203             var element;
1204             for (var i in this.options.list) {
1205                 element = this.options.list[i];
1206                 if (element.type == 'folder' && element.fullname == foldername) {
1207                     return true;
1208                 }
1209             }
1210             return false;
1211         },
1212         get_preference: function(name) {
1213             if (this.userprefs[name]) {
1214                 return this.userprefs[name];
1215             } else {
1216                 return false;
1217             }
1218         },
1219         set_preference: function(name, value) {
1220             if (this.userprefs[name] != value) {
1221                 require(['core_user/repository'], function(UserRepository) {
1222                     UserRepository.setUserPreference('filemanager_' + name, value);
1223                     this.userprefs[name] = value;
1224                 }.bind(this));
1225             }
1226         },
1227     });
1229     // finally init everything needed
1230     // hide loading picture, display filemanager interface
1231     var filemanager = Y.one('#filemanager-'+options.client_id);
1232     filemanager.removeClass('fm-loading').addClass('fm-loaded');
1234     var manager = new FileManagerHelper(options);
1235     var dndoptions = {
1236         filemanager: manager,
1237         acceptedtypes: options.filepicker.accepted_types,
1238         clientid: options.client_id,
1239         author: options.author,
1240         maxfiles: options.maxfiles,
1241         maxbytes: options.maxbytes,
1242         areamaxbytes: options.areamaxbytes,
1243         itemid: options.itemid,
1244         repositories: manager.filepicker_options.repositories,
1245         containerid: manager.dndcontainer.get('id'),
1246         contextid: options.context.id
1247     };
1248     M.form_dndupload.init(Y, dndoptions);