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 //For client side validation, remove hidden draft_id
57 Y.one('#id_'+options.elementname).set('value', '');
59 this.options = options;
60 if (options.mainfile) {
61 this.enablemainfile = options.mainfile;
63 this.client_id = options.client_id;
64 this.currentpath = '/';
65 this.maxfiles = options.maxfiles;
66 this.maxbytes = options.maxbytes;
68 this.filepicker_options = options.filepicker?options.filepicker:{};
69 this.filepicker_options.client_id = this.client_id;
70 this.filepicker_options.context = options.context;
71 this.filepicker_options.maxfiles = this.maxfiles;
72 this.filepicker_options.maxbytes = this.maxbytes;
73 this.filepicker_options.env = 'filemanager';
74 this.filepicker_options.itemid = options.itemid;
75 this.filepicker_options.elementname = options.elementname;
77 if (options.filecount) {
78 this.filecount = options.filecount;
86 wait: function(client_id) {
87 var container = Y.one('#filemanager-'+client_id);
88 container.set('innerHTML', '');
89 var html = Y.Node.create('<ul id="draftfiles-'+client_id+'"></ul>');
90 container.appendChild(html);
91 var panel = Y.one('#draftfiles-'+client_id);
93 var str = '<div style="text-align:center">';
94 str += '<img src="'+M.util.image_url('i/loading_small')+'" />';
97 panel.set('innerHTML', str);
102 request: function(args, redraw) {
103 var api = this.api + '?action='+args.action;
107 scope = args['scope'];
109 params['sesskey'] = M.cfg.sesskey;
110 params['client_id'] = this.client_id;
111 params['filepath'] = this.currentpath;
112 params['itemid'] = this.options.itemid?this.options.itemid:0;
113 if (args['params']) {
114 for (i in args['params']) {
115 params[i] = args['params'][i];
121 complete: function(id,o,p) {
126 var data = Y.JSON.parse(o.responseText);
127 args.callback(id,data,p);
134 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
135 'User-Agent': 'MoodleFileManager/3.0'
137 data: build_querystring(params)
140 cfg.form = args.form;
144 this.wait(this.client_id);
147 filepicker_callback: function(obj) {
148 var button_addfile = Y.one("#btnadd-"+this.client_id);
150 if (this.filecount > 0) {
151 Y.one("#btndwn-"+this.client_id).setStyle('display', 'inline');
153 if (this.filecount >= this.maxfiles && this.maxfiles!=-1) {
154 button_addfile.setStyle('display', 'none');
156 this.refresh(this.currentpath);
157 //When file is added then set draftid for validation
158 var elementname = M.core_filepicker.instances[this.client_id].options.elementname;
159 var itemid = M.core_filepicker.instances[this.client_id].options.itemid;
160 Y.one('#id_'+elementname).set('value', itemid);
161 //generate event to indicate changes which will be used by disable if code.
162 Y.one('#id_'+elementname).simulate('change');
164 refresh: function(filepath) {
166 this.currentpath = filepath;
168 filepath = this.currentpath;
170 this.currentpath = filepath;
175 params: {'filepath':filepath},
176 callback: function(id, obj, args) {
182 setup_buttons: function() {
183 var button_download = Y.one("#btndwn-"+this.client_id);
184 var button_create = Y.one("#btncrt-"+this.client_id);
185 var button_addfile = Y.one("#btnadd-"+this.client_id);
187 // setup 'add file' button
188 // if maxfiles == -1, the no limit
189 if (this.filecount >= this.maxfiles
190 && this.maxfiles!=-1) {
191 button_addfile.setStyle('display', 'none');
193 button_addfile.on('click', function(e) {
194 var options = this.filepicker_options;
195 options.formcallback = this.filepicker_callback;
196 // XXX: magic here, to let filepicker use filemanager scope
197 options.magicscope = this;
198 options.savepath = this.currentpath;
199 M.core_filepicker.show(Y, options);
203 // setup 'make a folder' button
204 if (this.options.subdirs) {
205 button_create.on('click',function(e) {
207 // a function used to perform an ajax request
208 function perform_action(e) {
209 var foldername = Y.one('#fm-newname').get('value');
215 params: {filepath:scope.currentpath, newdirname:foldername},
216 callback: function(id, obj, args) {
217 var filepath = obj.filepath;
218 scope.mkdir_dialog.hide();
219 scope.refresh(filepath);
220 Y.one('#fm-newname').set('value', '');
224 if (!Y.one('#fm-mkdir-dlg')) {
225 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>');
226 Y.one(document.body).appendChild(dialog);
227 this.mkdir_dialog = new YAHOO.widget.Dialog("fm-mkdir-dlg", {
232 constraintoviewport : true
236 var buttons = [ { text:M.str.moodle.ok, handler:perform_action, isDefault:true },
237 { text:M.str.moodle.cancel, handler:function(){this.cancel();}}];
239 this.mkdir_dialog.cfg.queueProperty("buttons", buttons);
240 this.mkdir_dialog.render();
241 this.mkdir_dialog.show();
244 button_create.setStyle('display', 'none');
247 // setup 'download this folder' button
248 // NOTE: popup window must be enabled to perform download process
249 button_download.on('click',function() {
251 // perform downloaddir ajax request
253 action: 'downloaddir',
255 callback: function(id, obj, args) {
257 scope.refresh(obj.filepath);
258 var win = window.open(obj.fileurl, 'fm-download-folder');
260 alert(M.str.repository.popupblockeddownload);
263 alert(M.str.repository.draftareanofiles);
269 empty_filelist: function(container) {
270 container.set('innerHTML', '<div class="mdl-align">'+M.str.repository.nofilesattached+'</div>');
273 var options = this.options;
274 var path = this.options.path;
275 var list = this.options.list;
276 var breadcrumb = Y.one('#fm-path-'+this.client_id);
280 breadcrumb.set('innerHTML', '');
285 arrow = Y.Node.create('<span>'+M.str.moodle.path + ': </span>');
287 arrow = Y.Node.create('<span> â–¶ </span>');
291 var pathid = 'fm-path-node-'+this.client_id;
292 pathid += ('-'+count);
294 var crumb = Y.Node.create('<a href="###" id="'+pathid+'">'+path[p].name+'</a>');
295 breadcrumb.appendChild(arrow);
296 breadcrumb.appendChild(crumb);
299 args.requestpath = path[p].path;
300 args.client_id = this.client_id;
301 Y.one('#'+pathid).on('click', function(e, args) {
304 params['filepath'] = args.requestpath;
305 this.currentpath = args.requestpath;
310 callback: function(id, obj, args) {
318 var template = Y.one('#fm-template');
319 var container = Y.one('#filemanager-' + this.client_id);
324 var folder_data = {};
326 // normal file list items
330 // archives list items
337 file_data.itemid = folder_data.itemid = zip_data.itemid = options.itemid;
338 file_data.client_id = folder_data.client_id = zip_data.client_id = this.client_id;
340 var foldername_ids = [];
341 if (!list || list.length == 0) {
342 // hide file browser and breadcrumb
343 //container.setStyle('display', 'none');
344 this.empty_filelist(container);
345 if (!path || path.length <= 1) {
346 breadcrumb.setStyle('display', 'none');
350 container.setStyle('display', 'block');
351 breadcrumb.setStyle('display', 'block');
357 // the li html element
358 var htmlid = 'fileitem-'+this.client_id+'-'+count;
360 var fileid = 'filename-'+this.client_id+'-'+count;
362 var action = 'action-' +this.client_id+'-'+count;
364 var html = template.get('innerHTML');
366 html_ids.push('#'+htmlid);
367 html_data[htmlid] = action;
369 list[i].htmlid = htmlid;
370 list[i].fileid = fileid;
371 list[i].action = action;
375 switch (list[i].type) {
378 foldername_ids.push('#'+fileid);
380 folder_ids.push('#'+action);
381 folder_data[action] = list[i];
382 folder_data[fileid] = list[i];
385 file_ids.push('#'+action);
387 file_ids.push('#'+fileid);
388 file_data[action] = list[i];
389 file_data[fileid] = list[i];
395 zip_ids.push('#'+action);
396 zip_ids.push('#'+fileid);
397 zip_data[action] = list[i];
398 zip_data[fileid] = list[i];
404 var fullname = list[i].fullname;
406 if (list[i].sortorder == 1) {
407 html = html.replace('___fullname___', '<strong><a title="'+fullname+'" href="'+url+'" id="'+fileid+'"><img src="'+list[i].icon+'" /> ' + fullname + '</a></strong>');
409 html = html.replace('___fullname___', '<a title="'+fullname+'" href="'+url+'" id="'+fileid+'"><img src="'+list[i].icon+'" /> ' + fullname + '</a>');
411 html = html.replace('___action___', '<span class="fm-menuicon" id="'+action+'"><img alt="â–¶" src="'+M.util.image_url('i/menu')+'" /></span>');
412 html = '<li id="'+htmlid+'">'+html+'</li>';
415 if (!Y.one('#draftfiles-'+this.client_id)) {
416 var filelist = Y.Node.create('<ul id="draftfiles-'+this.client_id+'"></ul>');
417 container.appendChild(filelist);
419 Y.one('#draftfiles-'+this.client_id).set('innerHTML', listhtml);
421 // click normal file menu
422 Y.on('click', this.create_filemenu, file_ids, this, file_data);
423 Y.on('contextmenu', this.create_filemenu, file_ids, this, file_data);
425 Y.on('click', this.create_foldermenu, folder_ids, this, folder_data);
426 Y.on('contextmenu', this.create_foldermenu, folder_ids, this, folder_data);
427 Y.on('contextmenu', this.create_foldermenu, foldername_ids, this, folder_data);
428 // click archievs menu
429 Y.on('click', this.create_zipmenu, zip_ids, this, zip_data);
430 Y.on('contextmenu', this.create_zipmenu, zip_ids, this, zip_data);
432 Y.on('click', this.enter_folder, foldername_ids, this, folder_data);
434 enter_folder: function(e, data) {
435 var node = e.currentTarget;
436 var file = data[node.get('id')];
437 this.refresh(file.filepath);
439 create_filemenu: function(e, data) {
441 var options = this.options;
442 var node = e.currentTarget;
443 var file = data[node.get('id')];
447 {text: M.str.moodle.download, url:file.url}
449 function setmainfile(type, ev, obj) {
450 var file = obj[node.get('id')];
451 //Y.one(mainid).set('value', file.filepath+file.filename);
453 params['filepath'] = file.filepath;
454 params['filename'] = file.filename;
456 action: 'setmainfile',
459 callback: function(id, obj, args) {
460 scope.refresh(scope.currentpath);
464 if (this.enablemainfile && (file.sortorder != 1)) {
465 var mainid = '#id_'+this.enablemainfile;
466 var menu = {text: M.str.repository.setmainfile, onclick:{fn: setmainfile, obj:data, scope:this}};
467 menuitems.push(menu);
469 this.create_menu(e, 'filemenu', menuitems, file, data);
471 create_foldermenu: function(e, data) {
474 var node = e.currentTarget;
475 var fileinfo = data[node.get('id')];
476 // an extra menu item for folder to zip it
477 function archive_folder(type,ev,obj) {
479 params['filepath'] = fileinfo.filepath;
480 params['filename'] = '.';
485 callback: function(id, obj, args) {
486 scope.refresh(obj.filepath);
491 {text: M.str.editor.zip, onclick: {fn: archive_folder, obj: data, scope: this}},
493 this.create_menu(e, 'foldermenu', menuitems, fileinfo, data);
495 create_zipmenu: function(e, data) {
498 var node = e.currentTarget;
499 var fileinfo = data[node.get('id')];
501 function unzip(type, ev, obj) {
503 params['filepath'] = fileinfo.filepath;
504 params['filename'] = fileinfo.fullname;
509 callback: function(id, obj, args) {
510 scope.refresh(obj.filepath);
515 {text: M.str.moodle.download, url:fileinfo.url},
516 {text: M.str.moodle.unzip, onclick: {fn: unzip, obj: data, scope: this}}
518 function setmainfile(type, ev, obj) {
519 var file = obj[node.get('id')];
520 //Y.one(mainid).set('value', file.filepath+file.filename);
522 params['filepath'] = file.filepath;
523 params['filename'] = file.filename;
525 action: 'setmainfile',
528 callback: function(id, obj, args) {
529 scope.refresh(scope.currentpath);
533 if (this.enablemainfile && (fileinfo.sortorder != 1)) {
534 var mainid = '#id_'+this.enablemainfile;
535 var menu = {text: M.str.repository.setmainfile, onclick:{fn: setmainfile, obj:data, scope:this}};
536 menuitems.push(menu);
538 this.create_menu(e, 'zipmenu', menuitems, fileinfo, data);
540 create_menu: function(ev, menuid, menuitems, fileinfo, options) {
541 var position = [ev.pageX, ev.pageY];
543 function remove(type, ev, obj) {
544 var dialog_options = {};
546 dialog_options.message = M.str.repository.confirmdeletefile;
547 dialog_options.scope = this;
550 if (fileinfo.type == 'folder') {
551 params.filename = '.';
552 params.filepath = fileinfo.filepath;
554 params.filename = fileinfo.fullname;
556 dialog_options.callbackargs = [params];
557 dialog_options.callback = function(params) {
562 callback: function(id, obj, args) {
564 scope.refresh(obj.filepath);
565 if (scope.filecount < scope.maxfiles && scope.maxfiles!=-1) {
566 var button_addfile = Y.one("#btnadd-"+scope.client_id);
567 button_addfile.setStyle('display', 'inline');
568 button_addfile.on('click', function(e) {
569 var options = scope.filepicker_options;
570 options.formcallback = scope.filepicker_callback;
571 // XXX: magic here, to let filepicker use filemanager scope
572 options.magicscope = scope;
573 options.savepath = scope.currentpath;
574 M.core_filepicker.show(Y, options);
580 M.util.show_confirm_dialog(ev, dialog_options);
582 function rename (type, ev, obj) {
584 var perform = function(e) {
585 var newfilename = Y.one('#fm-rename-input').get('value');
592 if (fileinfo.type == 'folder') {
593 params['filepath'] = fileinfo.filepath;
594 params['filename'] = '.';
595 params['newdirname'] = newfilename;
596 action = 'renamedir';
598 params['filepath'] = fileinfo.filepath;
599 params['filename'] = fileinfo.fullname;
600 params['newfilename'] = newfilename;
607 callback: function(id, obj, args) {
609 alert(M.str.repository.fileexists);
611 scope.refresh(obj.filepath);
613 Y.one('#fm-rename-input').set('value', '');
614 scope.rename_dialog.hide();
619 var dialog = Y.one('#fm-rename-dlg');
621 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>');
622 Y.one(document.body).appendChild(dialog);
623 this.rename_dialog = new YAHOO.widget.Dialog("fm-rename-dlg", {
627 constraintoviewport : true
631 var buttons = [ { text:M.str.moodle.rename, handler:perform, isDefault:true},
632 { text:M.str.moodle.cancel, handler:function(){this.cancel();}}];
634 this.rename_dialog.cfg.queueProperty('buttons', buttons);
635 this.rename_dialog.render();
636 this.rename_dialog.show();
637 //var k1 = new YAHOO.util.KeyListener(scope, {keys:13}, {fn:function(){perform();}, correctScope: true});
639 Y.one('#fm-rename-input').set('value', fileinfo.fullname);
641 function move(type, ev, obj) {
643 var itemid = this.options.itemid;
644 // setup move file dialog
646 if (!Y.one('#fm-move-dlg')) {
647 dialog = Y.Node.create('<div id="fm-move-dlg"></div>');
648 Y.one(document.body).appendChild(dialog);
650 dialog = Y.one('#fm-move-dlg');
653 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>');
655 this.movefile_dialog = new YAHOO.widget.Dialog("fm-move-dlg", {
659 constraintoviewport : true
662 var treeview = new YAHOO.widget.TreeView("fm-tree");
664 var dialog = this.movefile_dialog;
666 if (!treeview.targetpath) {
670 if (fileinfo.type == 'folder') {
675 params['filepath'] = fileinfo.filepath;
676 params['filename'] = fileinfo.fullname;
677 params['newfilepath'] = treeview.targetpath;
682 callback: function(id, obj, args) {
693 var buttons = [ { text:M.str.moodle.move, handler:_move, isDefault:true },
694 { text:M.str.moodle.cancel, handler:function(){this.cancel();}}];
696 this.movefile_dialog.cfg.queueProperty("buttons", buttons);
697 this.movefile_dialog.render();
699 treeview.subscribe("dblClickEvent", function(e) {
700 // update destidatoin folder
701 this.targetpath = e.node.data.path;
702 var title = Y.one('#fm-move-div');
703 title.set('innerHTML', '<strong>"' + this.targetpath + '"</strong> has been selected.');
706 function loadDataForNode(node, onCompleteCallback) {
708 params['filepath'] = node.data.path;
713 callback: function(id, obj, args) {
715 if (data.length == 0) {
718 for (var i in data) {
719 var textnode = {label: data[i].fullname, path: data[i].filepath, itemid: this.itemid};
720 var tmpNode = new YAHOO.widget.TextNode(textnode, node, false);
726 obj.oncomplete = onCompleteCallback;
730 this.movefile_dialog.subscribe('show', function(){
731 var rootNode = treeview.getRoot();
732 treeview.setDynamicLoad(loadDataForNode);
733 treeview.removeChildren(rootNode);
734 var textnode = {label: M.str.moodle.files, path: '/'};
735 var tmpNode = new YAHOO.widget.TextNode(textnode, rootNode, true);
739 this.movefile_dialog.show();
742 {text: M.str.moodle.rename+'...', onclick: {fn: rename, obj: options, scope: this}},
743 {text: M.str.moodle.move+'...', onclick: {fn: move, obj: options, scope: this}}
745 // delete is reserve word in Javascript
746 shared_items.push({text: M.str.moodle['delete']+'...', onclick: {fn: remove, obj: options, scope: this}});
747 var menu = new YAHOO.widget.Menu(menuid, {xy:position, clicktohide:true});
749 menu.addItems(menuitems);
750 menu.addItems(shared_items);
751 menu.render(document.body);
752 menu.subscribe('hide', function(){
753 this.fireEvent('destroy');
759 // finally init everything needed
760 // kill nonjs filemanager
761 var item = document.getElementById('nonjs-filemanager-'+options.client_id);
762 if (item && !options.usenonjs) {
763 item.parentNode.removeChild(item);
765 // hide loading picture
766 item = document.getElementById('filemanager-loading-'+options.client_id);
768 item.parentNode.removeChild(item);
770 // display filemanager interface
771 item = document.getElementById('filemanager-wrapper-'+options.client_id);
773 item.style.display = '';
776 new FileManagerHelper(options);