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