1 // This file is part of Moodle - http://moodle.org/
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.
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/>.
19 * this.api, stores the URL to make ajax request
21 * this.filepicker_options
22 * this.movefile_dialog
26 * this.filecount, how many files in this filemanager
30 * FileManager options:
32 * this.options.currentpath
37 M.form_filemanager = {};
40 * This fucntion is called for each file picker on page.
42 M.form_filemanager.init = function(Y, options) {
43 var FileManagerHelper = function(options) {
44 FileManagerHelper.superclass.constructor.apply(this, arguments);
46 FileManagerHelper.NAME = "FileManager";
47 FileManagerHelper.ATTRS = {
52 Y.extend(FileManagerHelper, Y.Base, {
53 api: M.cfg.wwwroot+'/repository/draftfiles_ajax.php',
55 initializer: function(options) {
56 this.options = options;
57 if (options.mainfile) {
58 this.enablemainfile = options.mainfile;
60 this.client_id = options.client_id;
61 this.currentpath = '/';
62 this.maxfiles = options.maxfiles;
63 this.maxbytes = options.maxbytes;
64 this.emptycallback = null; // Used by drag and drop upload
66 this.filepicker_options = options.filepicker?options.filepicker:{};
67 this.filepicker_options.client_id = this.client_id;
68 this.filepicker_options.context = options.context;
69 this.filepicker_options.maxfiles = this.maxfiles;
70 this.filepicker_options.maxbytes = this.maxbytes;
71 this.filepicker_options.env = 'filemanager';
72 this.filepicker_options.itemid = options.itemid;
74 if (options.filecount) {
75 this.filecount = options.filecount;
80 this.refresh(this.currentpath); // MDL-31113 get latest list from server
83 wait: function(client_id) {
84 var container = Y.one('#filemanager-'+client_id);
85 container.set('innerHTML', '');
86 var html = Y.Node.create('<ul id="draftfiles-'+client_id+'"></ul>');
87 container.appendChild(html);
88 var panel = Y.one('#draftfiles-'+client_id);
90 var str = '<div style="text-align:center">';
91 str += '<img src="'+M.util.image_url('i/loading_small')+'" />';
94 panel.set('innerHTML', str);
99 request: function(args, redraw) {
100 var api = this.api + '?action='+args.action;
104 scope = args['scope'];
106 params['sesskey'] = M.cfg.sesskey;
107 params['client_id'] = this.client_id;
108 params['filepath'] = this.currentpath;
109 params['itemid'] = this.options.itemid?this.options.itemid:0;
110 if (args['params']) {
111 for (i in args['params']) {
112 params[i] = args['params'][i];
118 complete: function(id,o,p) {
123 var data = Y.JSON.parse(o.responseText);
124 args.callback(id,data,p);
131 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
132 'User-Agent': 'MoodleFileManager/3.0'
134 data: build_querystring(params)
137 cfg.form = args.form;
141 this.wait(this.client_id);
144 filepicker_callback: function(obj) {
146 this.check_buttons();
147 this.refresh(this.currentpath);
148 if (typeof M.core_formchangechecker != 'undefined') {
149 M.core_formchangechecker.set_form_changed();
152 check_buttons: function() {
153 var button_addfile = Y.one("#btnadd-"+this.client_id);
154 if (this.filecount > 0) {
155 Y.one("#btndwn-"+this.client_id).setStyle('display', 'inline');
157 if (this.filecount >= this.maxfiles && this.maxfiles!=-1) {
158 button_addfile.setStyle('display', 'none');
161 refresh: function(filepath) {
163 this.currentpath = filepath;
165 filepath = this.currentpath;
167 this.currentpath = filepath;
172 params: {'filepath':filepath},
173 callback: function(id, obj, args) {
174 scope.filecount = obj.filecount;
175 scope.check_buttons();
181 setup_buttons: function() {
182 var button_download = Y.one("#btndwn-"+this.client_id);
183 var button_create = Y.one("#btncrt-"+this.client_id);
184 var button_addfile = Y.one("#btnadd-"+this.client_id);
186 // setup 'add file' button
187 // if maxfiles == -1, the no limit
188 if (this.filecount >= this.maxfiles
189 && this.maxfiles!=-1) {
190 button_addfile.setStyle('display', 'none');
192 button_addfile.on('click', function(e) {
193 var options = this.filepicker_options;
194 options.formcallback = this.filepicker_callback;
195 // XXX: magic here, to let filepicker use filemanager scope
196 options.magicscope = this;
197 options.savepath = this.currentpath;
198 M.core_filepicker.show(Y, options);
202 // setup 'make a folder' button
203 if (this.options.subdirs) {
204 button_create.on('click',function(e) {
206 // a function used to perform an ajax request
207 function perform_action(e) {
208 var foldername = Y.one('#fm-newname').get('value');
214 params: {filepath:scope.currentpath, newdirname:foldername},
215 callback: function(id, obj, args) {
216 var filepath = obj.filepath;
217 scope.mkdir_dialog.hide();
218 scope.refresh(filepath);
219 Y.one('#fm-newname').set('value', '');
220 if (typeof M.core_formchangechecker != 'undefined') {
221 M.core_formchangechecker.set_form_changed();
226 if (!Y.one('#fm-mkdir-dlg')) {
227 var dialog = Y.Node.create('<div id="fm-mkdir-dlg"><div class="hd">'+M.str.repository.entername+'</div><div class="bd"><input type="text" id="fm-newname" /></div></div>');
228 Y.one(document.body).appendChild(dialog);
229 this.mkdir_dialog = new YAHOO.widget.Dialog("fm-mkdir-dlg", {
234 constraintoviewport : true
238 var buttons = [ { text:M.str.moodle.ok, handler:perform_action, isDefault:true },
239 { text:M.str.moodle.cancel, handler:function(){this.cancel();}}];
241 this.mkdir_dialog.cfg.queueProperty("buttons", buttons);
242 this.mkdir_dialog.render();
243 this.mkdir_dialog.show();
246 button_create.setStyle('display', 'none');
249 // setup 'download this folder' button
250 // NOTE: popup window must be enabled to perform download process
251 button_download.on('click',function() {
253 // perform downloaddir ajax request
255 action: 'downloaddir',
257 callback: function(id, obj, args) {
259 scope.refresh(obj.filepath);
260 var win = window.open(obj.fileurl, 'fm-download-folder');
262 alert(M.str.repository.popupblockeddownload);
265 alert(M.str.repository.draftareanofiles);
271 empty_filelist: function(container) {
272 var content = '<div class="mdl-align">'+M.str.repository.nofilesattached;
273 content += '<span id="dndenabled2-'+this.client_id+'" style="display: none">';
274 content += ' - '+M.util.get_string('dndenabled_inbox', 'moodle')+'</span>';
276 content += this.upload_message();
277 container.set('innerHTML', content);
278 if (this.emptycallback) {
279 this.emptycallback(this.client_id);
282 upload_message: function() {
283 var div = '<div id="filemanager-uploadmessage'+this.client_id+'" style="display:none" class="dndupload-target">';
284 div += M.util.get_string('droptoupload', 'moodle');
289 var options = this.options;
290 var path = this.options.path;
291 var list = this.options.list;
292 var breadcrumb = Y.one('#fm-path-'+this.client_id);
296 breadcrumb.set('innerHTML', '');
301 arrow = Y.Node.create('<span>'+M.str.moodle.path + ': </span>');
303 arrow = Y.Node.create('<span> â–¶ </span>');
307 var pathid = 'fm-path-node-'+this.client_id;
308 pathid += ('-'+count);
310 var crumb = Y.Node.create('<a href="###" id="'+pathid+'">'+path[p].name+'</a>');
311 breadcrumb.appendChild(arrow);
312 breadcrumb.appendChild(crumb);
315 args.requestpath = path[p].path;
316 args.client_id = this.client_id;
317 Y.one('#'+pathid).on('click', function(e, args) {
320 params['filepath'] = args.requestpath;
321 this.currentpath = args.requestpath;
326 callback: function(id, obj, args) {
327 scope.filecount = obj.filecount;
328 scope.check_buttons();
336 var template = Y.one('#fm-template');
337 var container = Y.one('#filemanager-' + this.client_id);
342 var folder_data = {};
344 // normal file list items
348 // archives list items
355 file_data.itemid = folder_data.itemid = zip_data.itemid = options.itemid;
356 file_data.client_id = folder_data.client_id = zip_data.client_id = this.client_id;
358 var foldername_ids = [];
359 if (!list || list.length == 0) {
360 // hide file browser and breadcrumb
361 //container.setStyle('display', 'none');
362 this.empty_filelist(container);
363 if (!path || path.length <= 1) {
364 breadcrumb.setStyle('display', 'none');
368 container.setStyle('display', 'block');
369 breadcrumb.setStyle('display', 'block');
375 // the li html element
376 var htmlid = 'fileitem-'+this.client_id+'-'+count;
378 var fileid = 'filename-'+this.client_id+'-'+count;
380 var action = 'action-' +this.client_id+'-'+count;
382 var html = template.get('innerHTML');
384 html_ids.push('#'+htmlid);
385 html_data[htmlid] = action;
387 list[i].htmlid = htmlid;
388 list[i].fileid = fileid;
389 list[i].action = action;
393 switch (list[i].type) {
396 foldername_ids.push('#'+fileid);
398 folder_ids.push('#'+action);
399 folder_data[action] = list[i];
400 folder_data[fileid] = list[i];
403 file_ids.push('#'+action);
405 file_ids.push('#'+fileid);
406 file_data[action] = list[i];
407 file_data[fileid] = list[i];
413 zip_ids.push('#'+action);
414 zip_ids.push('#'+fileid);
415 zip_data[action] = list[i];
416 zip_data[fileid] = list[i];
422 var fullname = list[i].fullname;
424 if (list[i].sortorder == 1) {
425 html = html.replace('___fullname___', '<strong><a title="'+fullname+'" href="'+url+'" id="'+fileid+'"><img src="'+list[i].icon+'" /> ' + fullname + '</a></strong>');
427 html = html.replace('___fullname___', '<a title="'+fullname+'" href="'+url+'" id="'+fileid+'"><img src="'+list[i].icon+'" /> ' + fullname + '</a>');
429 html = html.replace('___action___', '<span class="fm-menuicon" id="'+action+'"><img alt="â–¶" src="'+M.util.image_url('i/menu')+'" /></span>');
430 html = '<li id="'+htmlid+'">'+html+'</li>';
433 if (!Y.one('#draftfiles-'+this.client_id)) {
434 var filelist = Y.Node.create('<ul id="draftfiles-'+this.client_id+'"></ul>');
435 container.appendChild(filelist);
437 listhtml += this.upload_message();
438 Y.one('#draftfiles-'+this.client_id).set('innerHTML', listhtml);
440 // click normal file menu
441 Y.on('click', this.create_filemenu, file_ids, this, file_data);
442 Y.on('contextmenu', this.create_filemenu, file_ids, this, file_data);
444 Y.on('click', this.create_foldermenu, folder_ids, this, folder_data);
445 Y.on('contextmenu', this.create_foldermenu, folder_ids, this, folder_data);
446 Y.on('contextmenu', this.create_foldermenu, foldername_ids, this, folder_data);
447 // click archievs menu
448 Y.on('click', this.create_zipmenu, zip_ids, this, zip_data);
449 Y.on('contextmenu', this.create_zipmenu, zip_ids, this, zip_data);
451 Y.on('click', this.enter_folder, foldername_ids, this, folder_data);
453 enter_folder: function(e, data) {
454 var node = e.currentTarget;
455 var file = data[node.get('id')];
456 this.refresh(file.filepath);
458 create_filemenu: function(e, data) {
460 var options = this.options;
461 var node = e.currentTarget;
462 var file = data[node.get('id')];
466 {text: M.str.moodle.download, onclick:{fn:open_file_in_new_window, obj:file, scope:this}}
468 function setmainfile(type, ev, obj) {
469 var file = obj[node.get('id')];
470 //Y.one(mainid).set('value', file.filepath+file.filename);
472 params['filepath'] = file.filepath;
473 params['filename'] = file.filename;
475 action: 'setmainfile',
478 callback: function(id, obj, args) {
479 scope.refresh(scope.currentpath);
483 function open_file_in_new_window(type, ev, obj) {
484 // We open in a new window rather than changing the current windows URL as we don't
485 // want to navigate away from the page
486 window.open(obj.url, 'fm-download-file');
488 if (this.enablemainfile && (file.sortorder != 1)) {
489 var mainid = '#id_'+this.enablemainfile;
490 var menu = {text: M.str.repository.setmainfile, onclick:{fn: setmainfile, obj:data, scope:this}};
491 menuitems.push(menu);
493 this.create_menu(e, 'filemenu', menuitems, file, data);
495 create_foldermenu: function(e, data) {
498 var node = e.currentTarget;
499 var fileinfo = data[node.get('id')];
500 // an extra menu item for folder to zip it
501 function archive_folder(type,ev,obj) {
503 params['filepath'] = fileinfo.filepath;
504 params['filename'] = '.';
509 callback: function(id, obj, args) {
510 scope.refresh(obj.filepath);
515 {text: M.str.editor.zip, onclick: {fn: archive_folder, obj: data, scope: this}},
517 this.create_menu(e, 'foldermenu', menuitems, fileinfo, data);
519 create_zipmenu: function(e, data) {
522 var node = e.currentTarget;
523 var fileinfo = data[node.get('id')];
525 function unzip(type, ev, obj) {
527 params['filepath'] = fileinfo.filepath;
528 params['filename'] = fileinfo.fullname;
533 callback: function(id, obj, args) {
534 scope.refresh(obj.filepath);
539 {text: M.str.moodle.download, url:fileinfo.url},
540 {text: M.str.moodle.unzip, onclick: {fn: unzip, obj: data, scope: this}}
542 function setmainfile(type, ev, obj) {
543 var file = obj[node.get('id')];
544 //Y.one(mainid).set('value', file.filepath+file.filename);
546 params['filepath'] = file.filepath;
547 params['filename'] = file.filename;
549 action: 'setmainfile',
552 callback: function(id, obj, args) {
553 scope.refresh(scope.currentpath);
557 if (this.enablemainfile && (fileinfo.sortorder != 1)) {
558 var mainid = '#id_'+this.enablemainfile;
559 var menu = {text: M.str.repository.setmainfile, onclick:{fn: setmainfile, obj:data, scope:this}};
560 menuitems.push(menu);
562 this.create_menu(e, 'zipmenu', menuitems, fileinfo, data);
564 create_menu: function(ev, menuid, menuitems, fileinfo, options) {
565 var position = [ev.pageX, ev.pageY];
567 function remove(type, ev, obj) {
568 var dialog_options = {};
570 dialog_options.message = M.str.repository.confirmdeletefile;
571 dialog_options.scope = this;
574 if (fileinfo.type == 'folder') {
575 params.filename = '.';
576 params.filepath = fileinfo.filepath;
578 params.filename = fileinfo.fullname;
580 dialog_options.callbackargs = [params];
581 dialog_options.callback = function(params) {
586 callback: function(id, obj, args) {
588 scope.refresh(obj.filepath);
589 if (typeof M.core_formchangechecker != 'undefined') {
590 M.core_formchangechecker.set_form_changed();
592 if (scope.filecount < scope.maxfiles && scope.maxfiles!=-1) {
593 var button_addfile = Y.one("#btnadd-"+scope.client_id);
594 button_addfile.setStyle('display', 'inline');
595 button_addfile.on('click', function(e) {
596 var options = scope.filepicker_options;
597 options.formcallback = scope.filepicker_callback;
598 // XXX: magic here, to let filepicker use filemanager scope
599 options.magicscope = scope;
600 options.savepath = scope.currentpath;
601 M.core_filepicker.show(Y, options);
607 M.util.show_confirm_dialog(ev, dialog_options);
609 function rename (type, ev, obj) {
611 var perform = function(e) {
612 var newfilename = Y.one('#fm-rename-input').get('value');
619 if (fileinfo.type == 'folder') {
620 params['filepath'] = fileinfo.filepath;
621 params['filename'] = '.';
622 params['newdirname'] = newfilename;
623 action = 'renamedir';
625 params['filepath'] = fileinfo.filepath;
626 params['filename'] = fileinfo.fullname;
627 params['newfilename'] = newfilename;
634 callback: function(id, obj, args) {
636 alert(M.str.repository.fileexists);
638 scope.refresh(obj.filepath);
639 if (typeof M.core_formchangechecker != 'undefined') {
640 M.core_formchangechecker.set_form_changed();
643 Y.one('#fm-rename-input').set('value', '');
644 scope.rename_dialog.hide();
649 var dialog = Y.one('#fm-rename-dlg');
651 dialog = Y.Node.create('<div id="fm-rename-dlg"><div class="hd">'+M.str.repository.enternewname+'</div><div class="bd"><input type="text" id="fm-rename-input" /></div></div>');
652 Y.one(document.body).appendChild(dialog);
653 this.rename_dialog = new YAHOO.widget.Dialog("fm-rename-dlg", {
657 constraintoviewport : true
661 var buttons = [ { text:M.str.moodle.rename, handler:perform, isDefault:true},
662 { text:M.str.moodle.cancel, handler:function(){this.cancel();}}];
664 this.rename_dialog.cfg.queueProperty('buttons', buttons);
665 this.rename_dialog.render();
666 this.rename_dialog.show();
667 //var k1 = new YAHOO.util.KeyListener(scope, {keys:13}, {fn:function(){perform();}, correctScope: true});
669 Y.one('#fm-rename-input').set('value', fileinfo.fullname);
671 function move(type, ev, obj) {
673 var itemid = this.options.itemid;
674 // setup move file dialog
676 if (!Y.one('#fm-move-dlg')) {
677 dialog = Y.Node.create('<div id="fm-move-dlg"></div>');
678 Y.one(document.body).appendChild(dialog);
680 dialog = Y.one('#fm-move-dlg');
683 dialog.set('innerHTML', '<div class="hd">'+M.str.repository.moving+'</div><div class="bd"><div id="fm-move-div">'+M.str.repository.nopathselected+'</div><div id="fm-tree"></div></div>');
685 this.movefile_dialog = new YAHOO.widget.Dialog("fm-move-dlg", {
689 constraintoviewport : true
692 var treeview = new YAHOO.widget.TreeView("fm-tree");
694 var dialog = this.movefile_dialog;
696 if (!treeview.targetpath) {
700 if (fileinfo.type == 'folder') {
705 params['filepath'] = fileinfo.filepath;
706 params['filename'] = fileinfo.fullname;
707 params['newfilepath'] = treeview.targetpath;
712 callback: function(id, obj, args) {
719 if (typeof M.core_formchangechecker != 'undefined') {
720 M.core_formchangechecker.set_form_changed();
726 var buttons = [ { text:M.str.moodle.move, handler:_move, isDefault:true },
727 { text:M.str.moodle.cancel, handler:function(){this.cancel();}}];
729 this.movefile_dialog.cfg.queueProperty("buttons", buttons);
730 this.movefile_dialog.render();
732 treeview.subscribe("dblClickEvent", function(e) {
733 // update destidatoin folder
734 this.targetpath = e.node.data.path;
735 var title = Y.one('#fm-move-div');
736 title.set('innerHTML', '<strong>"' + this.targetpath + '"</strong> has been selected.');
739 function loadDataForNode(node, onCompleteCallback) {
741 params['filepath'] = node.data.path;
746 callback: function(id, obj, args) {
748 if (data.length == 0) {
751 for (var i in data) {
752 var textnode = {label: data[i].fullname, path: data[i].filepath, itemid: this.itemid};
753 var tmpNode = new YAHOO.widget.TextNode(textnode, node, false);
759 obj.oncomplete = onCompleteCallback;
763 this.movefile_dialog.subscribe('show', function(){
764 var rootNode = treeview.getRoot();
765 treeview.setDynamicLoad(loadDataForNode);
766 treeview.removeChildren(rootNode);
767 var textnode = {label: M.str.moodle.files, path: '/'};
768 var tmpNode = new YAHOO.widget.TextNode(textnode, rootNode, true);
772 this.movefile_dialog.show();
775 {text: M.str.moodle.rename+'...', onclick: {fn: rename, obj: options, scope: this}},
776 {text: M.str.moodle.move+'...', onclick: {fn: move, obj: options, scope: this}}
778 // delete is reserve word in Javascript
779 shared_items.push({text: M.str.moodle['delete']+'...', onclick: {fn: remove, obj: options, scope: this}});
780 var menu = new YAHOO.widget.Menu(menuid, {xy:position, clicktohide:true});
782 menu.addItems(menuitems);
783 menu.addItems(shared_items);
784 menu.render(document.body);
785 menu.subscribe('hide', function(){
786 this.fireEvent('destroy');
792 // finally init everything needed
793 // kill nonjs filemanager
794 var item = document.getElementById('nonjs-filemanager-'+options.client_id);
795 if (item && !options.usenonjs) {
796 item.parentNode.removeChild(item);
798 // hide loading picture
799 item = document.getElementById('filemanager-loading-'+options.client_id);
801 item.parentNode.removeChild(item);
803 // display filemanager interface
804 item = document.getElementById('filemanager-wrapper-'+options.client_id);
806 item.style.display = '';
809 var manager = new FileManagerHelper(options);
811 filemanager: manager,
812 acceptedtypes: options.accepted_types,
813 clientid: options.client_id,
814 maxfiles: options.maxfiles,
815 maxbytes: options.maxbytes,
816 itemid: options.itemid,
817 repositories: manager.filepicker_options.repositories,
818 containerprefix: '#filemanager-',
820 M.form_dndupload.init(Y, dndoptions);