moodle/repository/filepicker.js
Paul Nicholls 80d92db940 MDL-36548: Major performance boost to Detail (list) view in file picker
Pass the data (filelist) into the DataTable when initialising it, rather than rendering an empty DataTable and then pushing all of the data into it via addRows().

It's also slightly more efficient to just pass the node in, rather than fetching the node and passing its ID in, as DataTable.render() will just re-select the node if given the ID. [Spotted by Andrew Nicols]
2012-11-14 12:36:13 +13:00

1935 lines
90 KiB
JavaScript

// YUI3 File Picker module for moodle
// Author: Dongsheng Cai <dongsheng@moodle.com>
/**
*
* File Picker UI
* =====
* this.fpnode, contains reference to filepicker Node, non-empty if and only if rendered
* this.api, stores the URL to make ajax request
* this.mainui, YUI Panel
* this.selectnode, contains reference to select-file Node
* this.selectui, YUI Panel for selecting particular file
* this.msg_dlg, YUI Panel for error or info message
* this.process_dlg, YUI Panel for processing existing filename
* this.treeview, YUI Treeview
* this.viewmode, store current view mode
* this.pathbar, reference to the Node with path bar
* this.pathnode, a Node element representing one folder in a path bar (not attached anywhere, just used for template)
* this.currentpath, the current path in the repository (or last requested path)
*
* Filepicker options:
* =====
* this.options.client_id, the instance id
* this.options.contextid
* this.options.itemid
* this.options.repositories, stores all repositories displaied in file picker
* this.options.formcallback
*
* Active repository options
* =====
* this.active_repo.id
* this.active_repo.nosearch
* this.active_repo.norefresh
* this.active_repo.nologin
* this.active_repo.help
* this.active_repo.manage
*
* Server responses
* =====
* this.filelist, cached filelist
* this.pages
* this.page
* this.filepath, current path (each element of the array is a part of the breadcrumb)
* this.logindata, cached login form
*/
YUI.add('moodle-core_filepicker', function(Y) {
/** help function to extract width/height style as a number, not as a string */
Y.Node.prototype.getStylePx = function(attr) {
var style = this.getStyle(attr);
if (''+style == '0' || ''+style == '0px') {
return 0;
}
var matches = style.match(/^([\d\.]+)px$/)
if (matches && parseFloat(matches[1])) {
return parseFloat(matches[1]);
}
return null;
}
/** if condition is met, the class is added to the node, otherwise - removed */
Y.Node.prototype.addClassIf = function(className, condition) {
if (condition) {
this.addClass(className);
} else {
this.removeClass(className);
}
return this;
}
/** sets the width(height) of the node considering existing minWidth(minHeight) */
Y.Node.prototype.setStyleAdv = function(stylename, value) {
var stylenameCap = stylename.substr(0,1).toUpperCase() + stylename.substr(1, stylename.length-1).toLowerCase();
this.setStyle(stylename, '' + Math.max(value, this.getStylePx('min'+stylenameCap)) + 'px')
return this;
}
/** set image source to src, if there is preview, remember it in lazyloading.
* If there is a preview and it was already loaded, use it. */
Y.Node.prototype.setImgSrc = function(src, realsrc, lazyloading) {
if (realsrc) {
if (M.core_filepicker.loadedpreviews[realsrc]) {
this.set('src', realsrc).addClass('realpreview');
return this;
} else {
if (!this.get('id')) {
this.generateID();
}
lazyloading[this.get('id')] = realsrc;
}
}
this.set('src', src);
return this;
}
/**
* Replaces the image source with preview. If the image is inside the treeview, we need
* also to update the html property of corresponding YAHOO.widget.HTMLNode
* @param array lazyloading array containing associations of imgnodeid->realsrc
*/
Y.Node.prototype.setImgRealSrc = function(lazyloading) {
if (this.get('id') && lazyloading[this.get('id')]) {
var newsrc = lazyloading[this.get('id')];
M.core_filepicker.loadedpreviews[newsrc] = true;
this.set('src', newsrc).addClass('realpreview');
delete lazyloading[this.get('id')];
var treenode = this.ancestor('.fp-treeview')
if (treenode && treenode.get('parentNode').treeview) {
treenode.get('parentNode').treeview.getRoot().refreshPreviews(this.get('id'), newsrc);
}
}
return this;
}
/** scan TreeView to find which node contains image with id=imgid and replace it's html
* with the new image source. */
Y.YUI2.widget.Node.prototype.refreshPreviews = function(imgid, newsrc, regex) {
if (!regex) {
regex = new RegExp("<img\\s[^>]*id=\""+imgid+"\"[^>]*?(/?)>", "im");
}
if (this.expanded || this.isLeaf) {
var html = this.getContentHtml();
if (html && this.setHtml && regex.test(html)) {
var newhtml = this.html.replace(regex, "<img id=\""+imgid+"\" src=\""+newsrc+"\" class=\"realpreview\"$1>", html);
this.setHtml(newhtml);
return true;
}
if (!this.isLeaf && this.children) {
for(var c in this.children) {
if (this.children[c].refreshPreviews(imgid, newsrc, regex)) {
return true;
}
}
}
}
return false;
}
/**
* Displays a list of files (used by filepicker, filemanager) inside the Node
*
* @param array options
* viewmode : 1 - icons, 2 - tree, 3 - table
* appendonly : whether fileslist need to be appended instead of replacing the existing content
* filenode : Node element that contains template for displaying one file
* callback : On click callback. The element of the fileslist array will be passed as argument
* rightclickcallback : On right click callback (optional).
* callbackcontext : context where callbacks are executed
* sortable : whether content may be sortable (in table mode)
* dynload : allow dynamic load for tree view
* filepath : for pre-building of tree view - the path to the current directory in filepicker format
* treeview_dynload : callback to function to dynamically load the folder in tree view
* classnamecallback : callback to function that returns the class name for an element
* @param array fileslist array of files to show, each array element may have attributes:
* title or fullname : file name
* shorttitle (optional) : display file name
* thumbnail : url of image
* icon : url of icon image
* thumbnail_width : width of thumbnail, default 90
* thumbnail_height : height of thumbnail, default 90
* thumbnail_alt : TODO not needed!
* description or thumbnail_title : alt text
* @param array lazyloading : reference to the array with lazy loading images
*/
Y.Node.prototype.fp_display_filelist = function(options, fileslist, lazyloading) {
var viewmodeclassnames = {1:'fp-iconview', 2:'fp-treeview', 3:'fp-tableview'};
var classname = viewmodeclassnames[options.viewmode];
var scope = this;
/** return whether file is a folder (different attributes in FileManager and FilePicker) */
var file_is_folder = function(node) {
if (node.children) {return true;}
if (node.type && node.type == 'folder') {return true;}
return false;
};
/** return the name of the file (different attributes in FileManager and FilePicker) */
var file_get_filename = function(node) {
return node.title ? node.title : node.fullname;
}
/** return display name of the file (different attributes in FileManager and FilePicker) */
var file_get_displayname = function(node) {
return node.shorttitle ? node.shorttitle : file_get_filename(node);
}
/** return file description (different attributes in FileManager and FilePicker) */
var file_get_description = function(node) {
return node.description ? node.description : (node.thumbnail_title ? node.thumbnail_title : file_get_filename(node));
}
/** help funciton for tree view */
var build_tree = function(node, level) {
// prepare file name with icon
var el = Y.Node.create('<div/>');
el.appendChild(options.filenode.cloneNode(true));
el.one('.fp-filename').setContent(file_get_displayname(node));
// TODO add tooltip with node.title or node.thumbnail_title
var tmpnodedata = {className:options.classnamecallback(node)};
el.get('children').addClass(tmpnodedata.className);
if (node.icon) {
el.one('.fp-icon').appendChild(Y.Node.create('<img/>'));
el.one('.fp-icon img').setImgSrc(node.icon, node.realicon, lazyloading);
}
// create node
tmpnodedata.html = el.getContent();
var tmpNode = new Y.YUI2.widget.HTMLNode(tmpnodedata, level, false);
if (node.dynamicLoadComplete) {
tmpNode.dynamicLoadComplete = true;
}
tmpNode.fileinfo = node;
tmpNode.isLeaf = !file_is_folder(node);
if (!tmpNode.isLeaf) {
if(node.expanded) {
tmpNode.expand();
}
tmpNode.path = node.path ? node.path : (node.filepath ? node.filepath : '');
for(var c in node.children) {
build_tree(node.children[c], tmpNode);
}
}
};
/** initialize tree view */
var initialize_tree_view = function() {
var parentid = scope.one('.'+classname).get('id');
// TODO MDL-32736 use YUI3 gallery TreeView
scope.treeview = new Y.YUI2.widget.TreeView(parentid);
if (options.dynload) {
scope.treeview.setDynamicLoad(Y.bind(options.treeview_dynload, options.callbackcontext), 1);
}
scope.treeview.singleNodeHighlight = true;
if (options.filepath && options.filepath.length) {
// we just jumped from icon/details view, we need to show all parents
// we extract as much information as possible from filepath and filelist
// and send additional requests to retrieve siblings for parent folders
var mytree = {};
var mytreeel = null;
for (var i in options.filepath) {
if (mytreeel == null) {
mytreeel = mytree;
} else {
mytreeel.children = [{}];
mytreeel = mytreeel.children[0];
}
var pathelement = options.filepath[i];
mytreeel.path = pathelement.path;
mytreeel.title = pathelement.name;
mytreeel.icon = pathelement.icon;
mytreeel.dynamicLoadComplete = true; // we will call it manually
mytreeel.expanded = true;
}
mytreeel.children = fileslist;
build_tree(mytree, scope.treeview.getRoot());
// manually call dynload for parent elements in the tree so we can load other siblings
if (options.dynload) {
var root = scope.treeview.getRoot();
while (root && root.children && root.children.length) {
root = root.children[0];
if (root.path == mytreeel.path) {
root.origpath = options.filepath;
root.origlist = fileslist;
} else if (!root.isLeaf && root.expanded) {
Y.bind(options.treeview_dynload, options.callbackcontext)(root, null);
}
}
}
} else {
// there is no path information, just display all elements as a list, without hierarchy
for(k in fileslist) {
build_tree(fileslist[k], scope.treeview.getRoot());
}
}
scope.treeview.subscribe('clickEvent', function(e){
e.node.highlight(false);
var callback = options.callback;
if (options.rightclickcallback && e.event.target &&
Y.Node(e.event.target).ancestor('.fp-treeview .fp-contextmenu', true)) {
callback = options.rightclickcallback;
}
Y.bind(callback, options.callbackcontext)(e, e.node.fileinfo);
Y.YUI2.util.Event.stopEvent(e.event)
});
// TODO MDL-32736 support right click
/*if (options.rightclickcallback) {
scope.treeview.subscribe('dblClickEvent', function(e){
e.node.highlight(false);
Y.bind(options.rightclickcallback, options.callbackcontext)(e, e.node.fileinfo);
});
}*/
scope.treeview.draw();
};
/** formatting function for table view */
var formatValue = function (o){
if (o.data[''+o.column.key+'_f_s']) {return o.data[''+o.column.key+'_f_s'];}
else if (o.data[''+o.column.key+'_f']) {return o.data[''+o.column.key+'_f'];}
else if (o.value) {return o.value;}
else {return '';}
};
/** formatting function for table view */
var formatTitle = function(o) {
var el = Y.Node.create('<div/>');
el.appendChild(options.filenode.cloneNode(true)); // TODO not node but string!
el.get('children').addClass(o.data['classname']);
el.one('.fp-filename').setContent(o.value);
if (o.data['icon']) {
el.one('.fp-icon').appendChild(Y.Node.create('<img/>'));
el.one('.fp-icon img').setImgSrc(o.data['icon'], o.data['realicon'], lazyloading);
}
if (options.rightclickcallback) {
el.get('children').addClass('fp-hascontextmenu');
}
// TODO add tooltip with o.data['title'] (o.value) or o.data['thumbnail_title']
return el.getContent();
}
/** sorting function for table view */
var sortFoldersFirst = function(a, b, desc) {
if (a.get('isfolder') && !b.get('isfolder')) {
return -1;
}
if (!a.get('isfolder') && b.get('isfolder')) {
return 1;
}
var aa = a.get(this.key), bb = b.get(this.key), dir = desc ? -1 : 1;
return (aa > bb) ? dir : ((aa < bb) ? -dir : 0);
}
/** initialize table view */
var initialize_table_view = function() {
var cols = [
{key: "displayname", label: M.str.moodle.name, allowHTML: true, formatter: formatTitle,
sortable: true, sortFn: sortFoldersFirst},
{key: "datemodified", label: M.str.moodle.lastmodified, allowHTML: true, formatter: formatValue,
sortable: true, sortFn: sortFoldersFirst},
{key: "size", label: M.str.repository.size, allowHTML: true, formatter: formatValue,
sortable: true, sortFn: sortFoldersFirst},
{key: "mimetype", label: M.str.repository.type, allowHTML: true,
sortable: true, sortFn: sortFoldersFirst}
];
for (var k in fileslist) {
// to speed up sorting and formatting
fileslist[k].displayname = file_get_displayname(fileslist[k]);
fileslist[k].isfolder = file_is_folder(fileslist[k]);
fileslist[k].classname = options.classnamecallback(fileslist[k]);
}
scope.tableview = new Y.DataTable({columns: cols, data: fileslist});
scope.tableview.delegate('click', function (e, tableview) {
var record = tableview.getRecord(e.currentTarget.get('id'));
if (record) {
var callback = options.callback;
if (options.rightclickcallback && e.target.ancestor('.fp-tableview .fp-contextmenu', true)) {
callback = options.rightclickcallback;
}
Y.bind(callback, this)(e, record.getAttrs());
}
}, 'tr', options.callbackcontext, scope.tableview);
if (options.rightclickcallback) {
scope.tableview.delegate('contextmenu', function (e, tableview) {
var record = tableview.getRecord(e.currentTarget.get('id'));
if (record) { Y.bind(options.rightclickcallback, this)(e, record.getAttrs()); }
}, 'tr', options.callbackcontext, scope.tableview);
}
}
/** append items in table view mode */
var append_files_table = function() {
var parentnode = scope.one('.'+classname);
scope.tableview.render(parentnode);
scope.tableview.sortable = options.sortable ? true : false;
};
/** append items in tree view mode */
var append_files_tree = function() {
if (options.appendonly) {
var parentnode = scope.treeview.getRoot();
if (scope.treeview.getHighlightedNode()) {
parentnode = scope.treeview.getHighlightedNode();
if (parentnode.isLeaf) {parentnode = parentnode.parent;}
}
for (var k in fileslist) {
build_tree(fileslist[k], parentnode);
}
scope.treeview.draw();
} else {
// otherwise files were already added in initialize_tree_view()
}
}
/** append items in icon view mode */
var append_files_icons = function() {
parent = scope.one('.'+classname);
for (var k in fileslist) {
var node = fileslist[k];
var element = options.filenode.cloneNode(true);
parent.appendChild(element);
element.addClass(options.classnamecallback(node));
var filenamediv = element.one('.fp-filename');
filenamediv.setContent(file_get_displayname(node));
var imgdiv = element.one('.fp-thumbnail'), width, height, src;
if (node.thumbnail) {
width = node.thumbnail_width ? node.thumbnail_width : 90;
height = node.thumbnail_height ? node.thumbnail_height : 90;
src = node.thumbnail;
} else {
width = 16;
height = 16;
src = node.icon;
}
filenamediv.setStyleAdv('width', width);
imgdiv.setStyleAdv('width', width).setStyleAdv('height', height);
var img = Y.Node.create('<img/>').setAttrs({
title: file_get_description(node),
alt: node.thumbnail_alt ? node.thumbnail_alt : file_get_filename(node)}).
setStyle('maxWidth', ''+width+'px').
setStyle('maxHeight', ''+height+'px');
img.setImgSrc(src, node.realthumbnail, lazyloading);
imgdiv.appendChild(img);
element.on('click', function(e, nd) {
if (options.rightclickcallback && e.target.ancestor('.fp-iconview .fp-contextmenu', true)) {
Y.bind(options.rightclickcallback, this)(e, nd);
} else {
Y.bind(options.callback, this)(e, nd);
}
}, options.callbackcontext, node);
if (options.rightclickcallback) {
element.on('contextmenu', options.rightclickcallback, options.callbackcontext, node);
}
}
}
// initialize files view
if (!options.appendonly) {
var parent = Y.Node.create('<div/>').addClass(classname);
this.setContent('').appendChild(parent);
parent.generateID();
if (options.viewmode == 2) {
initialize_tree_view();
} else if (options.viewmode == 3) {
initialize_table_view();
} else {
// nothing to initialize for icon view
}
}
// append files to the list
if (options.viewmode == 2) {
append_files_tree();
} else if (options.viewmode == 3) {
append_files_table();
} else {
append_files_icons();
}
}
/**
* creates a node and adds it to the div with id #filesskin. This is needed for CSS to be able
* to overwrite YUI skin styles (instead of using !important that does not work in IE)
*/
Y.Node.createWithFilesSkin = function(node) {
if (!Y.one('#filesskin')) {
Y.one(document.body).appendChild(Y.Node.create('<div/>').set('id', 'filesskin'));
}
return Y.one('#filesskin').appendChild(Y.Node.create(node));
}
}, '@VERSION@', {
requires:['base', 'node', 'yui2-treeview', 'panel', 'cookie', 'datatable', 'datatable-sort']
});
M.core_filepicker = M.core_filepicker || {};
/**
* instances of file pickers used on page
*/
M.core_filepicker.instances = M.core_filepicker.instances || {};
M.core_filepicker.active_filepicker = null;
/**
* HTML Templates to use in FilePicker
*/
M.core_filepicker.templates = M.core_filepicker.templates || {};
/**
* Array of image sources for real previews (realicon or realthumbnail) that are already loaded
*/
M.core_filepicker.loadedpreviews = M.core_filepicker.loadedpreviews || {};
/**
* Set selected file info
*
* @parma object file info
*/
M.core_filepicker.select_file = function(file) {
M.core_filepicker.active_filepicker.select_file(file);
}
/**
* Init and show file picker
*/
M.core_filepicker.show = function(Y, options) {
if (!M.core_filepicker.instances[options.client_id]) {
M.core_filepicker.init(Y, options);
}
M.core_filepicker.instances[options.client_id].show();
};
M.core_filepicker.set_templates = function(Y, templates) {
for (var templid in templates) {
M.core_filepicker.templates[templid] = templates[templid];
}
}
/**
* Add new file picker to current instances
*/
M.core_filepicker.init = function(Y, options) {
var FilePickerHelper = function(options) {
FilePickerHelper.superclass.constructor.apply(this, arguments);
};
FilePickerHelper.NAME = "FilePickerHelper";
FilePickerHelper.ATTRS = {
options: {},
lang: {}
};
Y.extend(FilePickerHelper, Y.Base, {
api: M.cfg.wwwroot+'/repository/repository_ajax.php',
cached_responses: {},
waitinterval : null, // When the loading template is being displayed and its animation is running this will be an interval instance.
initializer: function(options) {
this.options = options;
if (!this.options.savepath) {
this.options.savepath = '/';
}
},
destructor: function() {
},
request: function(args, redraw) {
var api = (args.api ? args.api : this.api) + '?action='+args.action;
var params = {};
var scope = args['scope'] ? args['scope'] : this;
params['repo_id']=args.repository_id;
params['p'] = args.path?args.path:'';
params['page'] = args.page?args.page:'';
params['env']=this.options.env;
// the form element only accept certain file types
params['accepted_types']=this.options.accepted_types;
params['sesskey'] = M.cfg.sesskey;
params['client_id'] = args.client_id;
params['itemid'] = this.options.itemid?this.options.itemid:0;
params['maxbytes'] = this.options.maxbytes?this.options.maxbytes:-1;
// The unlimited value of areamaxbytes is -1, it is defined by FILE_AREA_MAX_BYTES_UNLIMITED.
params['areamaxbytes'] = this.options.areamaxbytes ? this.options.areamaxbytes : -1;
if (this.options.context && this.options.context.id) {
params['ctx_id'] = this.options.context.id;
}
if (args['params']) {
for (i in args['params']) {
params[i] = args['params'][i];
}
}
if (args.action == 'upload') {
var list = [];
for(var k in params) {
var value = params[k];
if(value instanceof Array) {
for(var i in value) {
list.push(k+'[]='+value[i]);
}
} else {
list.push(k+'='+value);
}
}
params = list.join('&');
} else {
params = build_querystring(params);
}
var cfg = {
method: 'POST',
on: {
complete: function(id,o,p) {
if (!o) {
// TODO
alert('IO FATAL');
return;
}
var data = null;
try {
data = Y.JSON.parse(o.responseText);
} catch(e) {
scope.print_msg(M.str.repository.invalidjson, 'error');
scope.display_error(M.str.repository.invalidjson+'<pre>'+stripHTML(o.responseText)+'</pre>', 'invalidjson')
return;
}
// error checking
if (data && data.error) {
scope.print_msg(data.error, 'error');
if (args.onerror) {
args.onerror(id,data,p);
} else {
this.fpnode.one('.fp-content').setContent('');
}
return;
} else {
if (data.msg) {
scope.print_msg(data.msg, 'info');
}
// cache result if applicable
if (args.action != 'upload' && data.allowcaching) {
scope.cached_responses[params] = data;
}
// invoke callback
args.callback(id,data,p);
}
}
},
arguments: {
scope: scope
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: params,
context: this
};
if (args.form) {
cfg.form = args.form;
}
// check if result of the same request has been already cached. If not, request it
// (never applicable in case of form submission and/or upload action):
if (!args.form && args.action != 'upload' && scope.cached_responses[params]) {
args.callback(null, scope.cached_responses[params], {scope: scope})
} else {
Y.io(api, cfg);
if (redraw) {
this.wait();
}
}
},
/** displays the dialog and processes rename/overwrite if there is a file with the same name in the same filearea*/
process_existing_file: function(data) {
var scope = this;
var handleOverwrite = function(e) {
// overwrite
e.preventDefault();
var data = this.process_dlg.dialogdata;
var params = {}
params['existingfilename'] = data.existingfile.filename;
params['existingfilepath'] = data.existingfile.filepath;
params['newfilename'] = data.newfile.filename;
params['newfilepath'] = data.newfile.filepath;
this.hide_header();
this.request({
'params': params,
'scope': this,
'action':'overwrite',
'path': '',
'client_id': this.options.client_id,
'repository_id': this.active_repo.id,
'callback': function(id, o, args) {
scope.hide();
// editor needs to update url
// filemanager do nothing
if (scope.options.editor_target && scope.options.env == 'editor') {
scope.options.editor_target.value = data.existingfile.url;
scope.options.editor_target.onchange();
} else if (scope.options.env === 'filepicker') {
var fileinfo = {'client_id':scope.options.client_id,
'url':data.existingfile.url,
'file':data.existingfile.filename};
var formcallback_scope = scope.options.magicscope ? scope.options.magicscope : scope;
scope.options.formcallback.apply(formcallback_scope, [fileinfo]);
}
}
}, true);
}
var handleRename = function(e) {
// inserts file with the new name
e.preventDefault();
var scope = this;
var data = this.process_dlg.dialogdata;
if (scope.options.editor_target && scope.options.env == 'editor') {
scope.options.editor_target.value = data.newfile.url;
scope.options.editor_target.onchange();
}
scope.hide();
var formcallback_scope = scope.options.magicscope ? scope.options.magicscope : scope;
var fileinfo = {'client_id':scope.options.client_id,
'url':data.newfile.url,
'file':data.newfile.filename};
scope.options.formcallback.apply(formcallback_scope, [fileinfo]);
}
var handleCancel = function(e) {
// Delete tmp file
e.preventDefault();
var params = {};
params['newfilename'] = this.process_dlg.dialogdata.newfile.filename;
params['newfilepath'] = this.process_dlg.dialogdata.newfile.filepath;
this.request({
'params': params,
'scope': this,
'action':'deletetmpfile',
'path': '',
'client_id': this.options.client_id,
'repository_id': this.active_repo.id,
'callback': function(id, o, args) {
// let it be in background, from user point of view nothing is happenning
}
}, false);
this.process_dlg.hide();
this.selectui.hide();
}
if (!this.process_dlg) {
this.process_dlg_node = Y.Node.createWithFilesSkin(M.core_filepicker.templates.processexistingfile);
var node = this.process_dlg_node;
node.generateID();
this.process_dlg = new Y.Panel({
srcNode : node,
headerContent: M.str.repository.fileexistsdialogheader,
zIndex : 8000,
centered : true,
modal : true,
visible : false,
render : true,
buttons : {}
});
this.process_dlg.plug(Y.Plugin.Drag,{handles:['#'+node.get('id')+' .yui3-widget-hd']});
node.one('.fp-dlg-butoverwrite').on('click', handleOverwrite, this);
node.one('.fp-dlg-butrename').on('click', handleRename, this);
node.one('.fp-dlg-butcancel').on('click', handleCancel, this);
if (this.options.env == 'editor') {
node.one('.fp-dlg-text').setContent(M.str.repository.fileexistsdialog_editor);
} else {
node.one('.fp-dlg-text').setContent(M.str.repository.fileexistsdialog_filemanager);
}
}
this.selectnode.removeClass('loading');
this.process_dlg.dialogdata = data;
this.process_dlg_node.one('.fp-dlg-butrename').setContent(M.util.get_string('renameto', 'repository', data.newfile.filename));
this.process_dlg.show();
},
/** displays error instead of filepicker contents */
display_error: function(errortext, errorcode) {
this.fpnode.one('.fp-content').setContent(M.core_filepicker.templates.error);
this.fpnode.one('.fp-content .fp-error').
addClass(errorcode).
setContent(errortext);
},
/** displays message in a popup */
print_msg: function(msg, type) {
var header = M.str.moodle.error;
if (type != 'error') {
type = 'info'; // one of only two types excepted
header = M.str.moodle.info;
}
if (!this.msg_dlg) {
this.msg_dlg_node = Y.Node.createWithFilesSkin(M.core_filepicker.templates.message);
this.msg_dlg_node.generateID();
this.msg_dlg = new Y.Panel({
srcNode : this.msg_dlg_node,
zIndex : 8000,
centered : true,
modal : true,
visible : false,
render : true
});
this.msg_dlg.plug(Y.Plugin.Drag,{handles:['#'+this.msg_dlg_node.get('id')+' .yui3-widget-hd']});
this.msg_dlg_node.one('.fp-msg-butok').on('click', function(e) {
e.preventDefault();
this.msg_dlg.hide();
}, this);
}
this.msg_dlg.set('headerContent', header);
this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
this.msg_dlg_node.one('.fp-msg-text').setContent(msg);
this.msg_dlg.show();
},
view_files: function(appenditems) {
this.viewbar_set_enabled(true);
this.print_path();
/*if ((appenditems == null) && (!this.filelist || !this.filelist.length) && !this.active_repo.hasmorepages) {
// TODO do it via classes and adjust for each view mode!
// If there are no items and no next page, just display status message and quit
this.display_error(M.str.repository.nofilesavailable, 'nofilesavailable');
return;
}*/
if (this.viewmode == 2) {
this.view_as_list(appenditems);
} else if (this.viewmode == 3) {
this.view_as_table(appenditems);
} else {
this.view_as_icons(appenditems);
}
// display/hide the link for requesting next page
if (!appenditems && this.active_repo.hasmorepages) {
if (!this.fpnode.one('.fp-content .fp-nextpage')) {
this.fpnode.one('.fp-content').append(M.core_filepicker.templates.nextpage);
}
this.fpnode.one('.fp-content .fp-nextpage').one('a,button').on('click', function(e) {
e.preventDefault();
this.fpnode.one('.fp-content .fp-nextpage').addClass('loading');
this.request_next_page();
}, this);
}
if (!this.active_repo.hasmorepages && this.fpnode.one('.fp-content .fp-nextpage')) {
this.fpnode.one('.fp-content .fp-nextpage').remove();
}
if (this.fpnode.one('.fp-content .fp-nextpage')) {
this.fpnode.one('.fp-content .fp-nextpage').removeClass('loading');
}
this.content_scrolled();
},
content_scrolled: function(e) {
setTimeout(Y.bind(function() {
if (this.processingimages) {
return;
}
this.processingimages = true;
var scope = this,
fpcontent = this.fpnode.one('.fp-content'),
fpcontenty = fpcontent.getY(),
fpcontentheight = fpcontent.getStylePx('height'),
nextpage = fpcontent.one('.fp-nextpage'),
is_node_visible = function(node) {
var offset = node.getY()-fpcontenty;
if (offset <= fpcontentheight && (offset >=0 || offset+node.getStylePx('height')>=0)) {
return true;
}
return false;
};
// automatically load next page when 'more' link becomes visible
if (nextpage && !nextpage.hasClass('loading') && is_node_visible(nextpage)) {
nextpage.one('a,button').simulate('click');
}
// replace src for visible images that need to be lazy-loaded
if (scope.lazyloading) {
fpcontent.all('img').each( function(node) {
if (node.get('id') && scope.lazyloading[node.get('id')] && is_node_visible(node)) {
node.setImgRealSrc(scope.lazyloading);
}
});
}
this.processingimages = false;
}, this), 200)
},
treeview_dynload: function(node, cb) {
var retrieved_children = {};
if (node.children) {
for (var i in node.children) {
retrieved_children[node.children[i].path] = node.children[i];
}
}
this.request({
action:'list',
client_id: this.options.client_id,
repository_id: this.active_repo.id,
path:node.path?node.path:'',
page:node.page?args.page:'',
scope:this,
callback: function(id, obj, args) {
var list = obj.list;
var scope = args.scope;
// check that user did not leave the view mode before recieving this response
if (!(scope.active_repo.id == obj.repo_id && scope.viewmode == 2 && node && node.getChildrenEl())) {
return;
}
if (cb != null) { // (in manual mode do not update current path)
scope.viewbar_set_enabled(true);
scope.parse_repository_options(obj);
}
node.highlight(false);
node.origlist = obj.list ? obj.list : null;
node.origpath = obj.path ? obj.path : null;
node.children = [];
for(k in list) {
if (list[k].children && retrieved_children[list[k].path]) {
// if this child is a folder and has already been retrieved
node.children[node.children.length] = retrieved_children[list[k].path];
} else {
// append new file to the list
scope.view_as_list([list[k]]);
}
}
if (cb == null) {
node.refresh();
} else {
// invoke callback requested by TreeView component
cb();
}
scope.content_scrolled();
}
}, false);
},
classnamecallback : function(node) {
var classname = '';
if (node.children) {
classname = classname + ' fp-folder';
}
if (node.isref) {
classname = classname + ' fp-isreference';
}
if (node.refcount) {
classname = classname + ' fp-hasreferences';
}
if (node.originalmissing) {
classname = classname + ' fp-originalmissing';
}
return Y.Lang.trim(classname);
},
/** displays list of files in tree (list) view mode. If param appenditems is specified,
* appends those items to the end of the list. Otherwise (default behaviour)
* clears the contents and displays the items from this.filelist */
view_as_list: function(appenditems) {
var list = (appenditems != null) ? appenditems : this.filelist;
this.viewmode = 2;
if (!this.filelist || this.filelist.length==0 && (!this.filepath || !this.filepath.length)) {
this.display_error(M.str.repository.nofilesavailable, 'nofilesavailable');
return;
}
var element_template = Y.Node.create(M.core_filepicker.templates.listfilename);
var options = {
viewmode : this.viewmode,
appendonly : (appenditems != null),
filenode : element_template,
callbackcontext : this,
callback : function(e, node) {
// TODO MDL-32736 e is not an event here but an object with properties 'event' and 'node'
if (!node.children) {
if (e.node.parent && e.node.parent.origpath) {
// set the current path
this.filepath = e.node.parent.origpath;
this.filelist = e.node.parent.origlist;
this.print_path();
}
this.select_file(node);
} else {
// save current path and filelist (in case we want to jump to other viewmode)
this.filepath = e.node.origpath;
this.filelist = e.node.origlist;
this.currentpath = e.node.path;
this.print_path();
this.content_scrolled();
}
},
classnamecallback : this.classnamecallback,
dynload : this.active_repo.dynload,
filepath : this.filepath,
treeview_dynload : this.treeview_dynload
};
this.fpnode.one('.fp-content').fp_display_filelist(options, list, this.lazyloading);
},
/** displays list of files in icon view mode. If param appenditems is specified,
* appends those items to the end of the list. Otherwise (default behaviour)
* clears the contents and displays the items from this.filelist */
view_as_icons: function(appenditems) {
this.viewmode = 1;
var list = (appenditems != null) ? appenditems : this.filelist;
var element_template = Y.Node.create(M.core_filepicker.templates.iconfilename);
if ((appenditems == null) && (!this.filelist || !this.filelist.length)) {
this.display_error(M.str.repository.nofilesavailable, 'nofilesavailable');
return;
}
var options = {
viewmode : this.viewmode,
appendonly : (appenditems != null),
filenode : element_template,
callbackcontext : this,
callback : function(e, node) {
if (e.preventDefault) {
e.preventDefault();
}
if(node.children) {
if (this.active_repo.dynload) {
this.list({'path':node.path});
} else {
this.filelist = node.children;
this.view_files();
}
} else {
this.select_file(node);
}
},
classnamecallback : this.classnamecallback
};
this.fpnode.one('.fp-content').fp_display_filelist(options, list, this.lazyloading);
},
/** displays list of files in table view mode. If param appenditems is specified,
* appends those items to the end of the list. Otherwise (default behaviour)
* clears the contents and displays the items from this.filelist */
view_as_table: function(appenditems) {
this.viewmode = 3;
var list = (appenditems != null) ? appenditems : this.filelist;
if (!appenditems && (!this.filelist || this.filelist.length==0) && !this.active_repo.hasmorepages) {
this.display_error(M.str.repository.nofilesavailable, 'nofilesavailable');
return;
}
var element_template = Y.Node.create(M.core_filepicker.templates.listfilename);
var options = {
viewmode : this.viewmode,
appendonly : (appenditems != null),
filenode : element_template,
callbackcontext : this,
sortable : !this.active_repo.hasmorepages,
callback : function(e, node) {
if (e.preventDefault) {e.preventDefault();}
if (node.children) {
if (this.active_repo.dynload) {
this.list({'path':node.path});
} else {
this.filelist = node.children;
this.view_files();
}
} else {
this.select_file(node);
}
},
classnamecallback : this.classnamecallback
};
this.fpnode.one('.fp-content').fp_display_filelist(options, list, this.lazyloading);
},
/** If more than one page available, requests and displays the files from the next page */
request_next_page: function() {
if (!this.active_repo.hasmorepages || this.active_repo.nextpagerequested) {
// nothing to load
return;
}
this.active_repo.nextpagerequested = true;
var nextpage = this.active_repo.page+1;
var args = {
page: nextpage,
repo_id: this.active_repo.id
};
var action = this.active_repo.issearchresult ? 'search' : 'list';
this.request({
path: this.currentpath,
scope: this,
action: action,
client_id: this.options.client_id,
repository_id: args.repo_id,
params: args,
callback: function(id, obj, args) {
var scope = args.scope;
// Check that we are still in the same repository and are expecting this page. We have no way
// to compare the requested page and the one returned, so we assume that if the last chunk
// of the breadcrumb is similar, then we probably are on the same page.
var samepage = true;
if (obj.path && scope.filepath) {
var pathbefore = scope.filepath[scope.filepath.length-1];
var pathafter = obj.path[obj.path.length-1];
if (pathbefore.path != pathafter.path) {
samepage = false;
}
}
if (scope.active_repo.hasmorepages && obj.list && obj.page &&
obj.repo_id == scope.active_repo.id &&
obj.page == scope.active_repo.page+1 && samepage) {
scope.parse_repository_options(obj, true);
scope.view_files(obj.list)
}
}
}, false);
},
select_file: function(args) {
this.selectui.show();
var client_id = this.options.client_id;
var selectnode = this.selectnode;
var return_types = this.options.repositories[this.active_repo.id].return_types;
selectnode.removeClass('loading');
selectnode.one('.fp-saveas input').set('value', args.title);
var imgnode = Y.Node.create('<img/>').
set('src', args.realthumbnail ? args.realthumbnail : args.thumbnail).
setStyle('maxHeight', ''+(args.thumbnail_height ? args.thumbnail_height : 90)+'px').
setStyle('maxWidth', ''+(args.thumbnail_width ? args.thumbnail_width : 90)+'px');
selectnode.one('.fp-thumbnail').setContent('').appendChild(imgnode);
// filelink is the array of file-link-types available for this repository in this env
var filelinktypes = [2/*FILE_INTERNAL*/,1/*FILE_EXTERNAL*/,4/*FILE_REFERENCE*/];
var filelink = {}, firstfilelink = null, filelinkcount = 0;
for (var i in filelinktypes) {
var allowed = (return_types & filelinktypes[i]) &&
(this.options.return_types & filelinktypes[i]);
if (filelinktypes[i] == 1/*FILE_EXTERNAL*/ && !this.options.externallink && this.options.env == 'editor') {
// special configuration setting 'repositoryallowexternallinks' may prevent
// using external links in editor environment
allowed = false;
}
filelink[filelinktypes[i]] = allowed;
firstfilelink = (firstfilelink==null && allowed) ? filelinktypes[i] : firstfilelink;
filelinkcount += allowed ? 1 : 0;
}
// make radio buttons enabled if this file-link-type is available and only if there are more than one file-link-type option
// check the first available file-link-type option
for (var linktype in filelink) {
var el = selectnode.one('.fp-linktype-'+linktype);
el.addClassIf('uneditable', !(filelink[linktype] && filelinkcount>1));
el.one('input').set('disabled', (filelink[linktype] && filelinkcount>1) ? '' : 'disabled').
set('checked', (firstfilelink == linktype) ? 'checked' : '').simulate('change')
}
// TODO MDL-32532: attributes 'hasauthor' and 'haslicense' need to be obsolete,
selectnode.one('.fp-setauthor input').set('value', args.author ? args.author : this.options.author);
this.set_selected_license(selectnode.one('.fp-setlicense'), args.license);
selectnode.one('form #filesource-'+client_id).set('value', args.source);
// display static information about a file (when known)
var attrs = ['datemodified','datecreated','size','license','author','dimensions'];
for (var i in attrs) {
if (selectnode.one('.fp-'+attrs[i])) {
var value = (args[attrs[i]+'_f']) ? args[attrs[i]+'_f'] : (args[attrs[i]] ? args[attrs[i]] : '');
selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '')
.one('.fp-value').setContent(value);
}
}
},
setup_select_file: function() {
var client_id = this.options.client_id;
var selectnode = this.selectnode;
var getfile = selectnode.one('.fp-select-confirm');
// bind labels with corresponding inputs
selectnode.all('.fp-saveas,.fp-linktype-2,.fp-linktype-1,.fp-linktype-4,.fp-setauthor,.fp-setlicense').each(function (node) {
node.all('label').set('for', node.one('input,select').generateID());
});
selectnode.one('.fp-linktype-2 input').setAttrs({value: 2, name: 'linktype'});
selectnode.one('.fp-linktype-1 input').setAttrs({value: 1, name: 'linktype'});
selectnode.one('.fp-linktype-4 input').setAttrs({value: 4, name: 'linktype'});
var changelinktype = function(e) {
if (e.currentTarget.get('checked')) {
var allowinputs = e.currentTarget.get('value') != 1/*FILE_EXTERNAL*/;
selectnode.all('.fp-setauthor,.fp-setlicense,.fp-saveas').each(function(node){
node.addClassIf('uneditable', !allowinputs);
node.all('input,select').set('disabled', allowinputs?'':'disabled');
});
}
};
selectnode.all('.fp-linktype-2,.fp-linktype-1,.fp-linktype-4').each(function (node) {
node.one('input').on('change', changelinktype, this);
});
this.populate_licenses_select(selectnode.one('.fp-setlicense select'));
// register event on clicking submit button
getfile.on('click', function(e) {
e.preventDefault();
var client_id = this.options.client_id;
var scope = this;
var repository_id = this.active_repo.id;
var title = selectnode.one('.fp-saveas input').get('value');
var filesource = selectnode.one('form #filesource-'+client_id).get('value');
var params = {'title':title, 'source':filesource, 'savepath': this.options.savepath};
var license = selectnode.one('.fp-setlicense select');
if (license) {
params['license'] = license.get('value');
var origlicense = selectnode.one('.fp-license .fp-value');
if (origlicense) {
origlicense = origlicense.getContent();
}
this.set_preference('recentlicense', license.get('value'));
}
params['author'] = selectnode.one('.fp-setauthor input').get('value');
var return_types = this.options.repositories[this.active_repo.id].return_types;
if (this.options.env == 'editor') {
// in editor, images are stored in '/' only
params.savepath = '/';
}
if ((this.options.externallink || this.options.env != 'editor') &&
(return_types & 1/*FILE_EXTERNAL*/) &&
(this.options.return_types & 1/*FILE_EXTERNAL*/) &&
selectnode.one('.fp-linktype-1 input').get('checked')) {
params['linkexternal'] = 'yes';
} else if ((return_types & 4/*FILE_REFERENCE*/) &&
(this.options.return_types & 4/*FILE_REFERENCE*/) &&
selectnode.one('.fp-linktype-4 input').get('checked')) {
params['usefilereference'] = '1';
}
selectnode.addClass('loading');
this.request({
action:'download',
client_id: client_id,
repository_id: repository_id,
'params': params,
onerror: function(id, obj, args) {
selectnode.removeClass('loading');
scope.selectui.hide();
},
callback: function(id, obj, args) {
selectnode.removeClass('loading');
if (obj.event == 'fileexists') {
scope.process_existing_file(obj);
return;
}
if (scope.options.editor_target && scope.options.env=='editor') {
scope.options.editor_target.value=obj.url;
scope.options.editor_target.onchange();
}
scope.hide();
obj.client_id = client_id;
var formcallback_scope = args.scope.options.magicscope ? args.scope.options.magicscope : args.scope;
scope.options.formcallback.apply(formcallback_scope, [obj]);
}
}, false);
}, this);
var elform = selectnode.one('form');
elform.appendChild(Y.Node.create('<input/>').
setAttrs({type:'hidden',id:'filesource-'+client_id}));
elform.on('keydown', function(e) {
if (e.keyCode == 13) {
getfile.simulate('click');
e.preventDefault();
}
}, this);
var cancel = selectnode.one('.fp-select-cancel');
cancel.on('click', function(e) {
e.preventDefault();
this.selectui.hide();
}, this);
},
wait: function() {
// First check there isn't already an interval in play, and if there is kill it now.
if (this.waitinterval != null) {
clearInterval(this.waitinterval);
}
// Prepare the root node we will set content for and the loading template we want to display as a YUI node.
var root = this.fpnode.one('.fp-content');
var content = Y.Node.create(M.core_filepicker.templates.loading).addClass('fp-content-hidden').setStyle('opacity', 0);
var count = 0;
// Initiate an interval, we will have a count which will increment every 100 milliseconds.
// Count 0 - the loading icon will have visibility set to hidden (invisible) and have an opacity of 0 (invisible also)
// Count 5 - the visiblity will be switched to visible but opacity will still be at 0 (inivisible)
// Counts 6 - 15 opacity will be increased by 0.1 making the loading icon visible over the period of a second
// Count 16 - The interval will be cancelled.
var interval = setInterval(function(){
if (!content || !root.contains(content) || count >= 15) {
clearInterval(interval);
return true;
}
if (count == 5) {
content.removeClass('fp-content-hidden');
} else if (count > 5) {
var opacity = parseFloat(content.getStyle('opacity'));
content.setStyle('opacity', opacity + 0.1);
}
count++;
return false;
}, 100);
// Store the wait interval so that we can check it in the future.
this.waitinterval = interval;
// Set the content to the loading template.
root.setContent(content);
},
viewbar_set_enabled: function(mode) {
var viewbar = this.fpnode.one('.fp-viewbar')
if (viewbar) {
if (mode) {
viewbar.addClass('enabled').removeClass('disabled')
} else {
viewbar.removeClass('enabled').addClass('disabled')
}
}
this.fpnode.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked')
var modes = {1:'icons', 2:'tree', 3:'details'};
this.fpnode.all('.fp-vb-'+modes[this.viewmode]).addClass('checked');
},
viewbar_clicked: function(e) {
e.preventDefault();
var viewbar = this.fpnode.one('.fp-viewbar')
if (!viewbar || !viewbar.hasClass('disabled')) {
if (e.currentTarget.hasClass('fp-vb-tree')) {
this.viewmode = 2;
} else if (e.currentTarget.hasClass('fp-vb-details')) {
this.viewmode = 3;
} else {
this.viewmode = 1;
}
this.viewbar_set_enabled(true)
this.view_files();
this.set_preference('recentviewmode', this.viewmode);
}
},
render: function() {
var client_id = this.options.client_id;
this.fpnode = Y.Node.createWithFilesSkin(M.core_filepicker.templates.generallayout).
set('id', 'filepicker-'+client_id);
this.mainui = new Y.Panel({
srcNode : this.fpnode,
headerContent: M.str.repository.filepicker,
zIndex : 5000,
centered : true,
modal : true,
visible : false,
minWidth : this.fpnode.getStylePx('minWidth'),
minHeight : this.fpnode.getStylePx('minHeight'),
maxWidth : this.fpnode.getStylePx('maxWidth'),
maxHeight : this.fpnode.getStylePx('maxHeight'),
render : true
});
// allow to move the panel dragging it by it's header:
this.mainui.plug(Y.Plugin.Drag,{handles:['#filepicker-'+client_id+' .yui3-widget-hd']});
this.mainui.show();
if (this.mainui.get('y') < 0) {
this.mainui.set('y', 0);
}
// create panel for selecting a file (initially hidden)
this.selectnode = Y.Node.createWithFilesSkin(M.core_filepicker.templates.selectlayout).
set('id', 'filepicker-select-'+client_id);
this.selectui = new Y.Panel({
srcNode : this.selectnode,
zIndex : 6000,
centered : true,
modal : true,
close : true,
render : true
});
// allow to move the panel dragging it by it's header:
this.selectui.plug(Y.Plugin.Drag,{handles:['#filepicker-select-'+client_id+' .yui3-widget-hd']});
this.selectui.hide();
// event handler for lazy loading of thumbnails and next page
this.fpnode.one('.fp-content').on(['scroll','resize'], this.content_scrolled, this);
// save template for one path element and location of path bar
if (this.fpnode.one('.fp-path-folder')) {
this.pathnode = this.fpnode.one('.fp-path-folder');
this.pathbar = this.pathnode.get('parentNode');
this.pathbar.removeChild(this.pathnode);
}
// assign callbacks for view mode switch buttons
this.fpnode.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').
on('click', this.viewbar_clicked, this);
// assign callbacks for toolbar links
this.setup_toolbar();
this.setup_select_file();
this.hide_header();
// processing repository listing
// Resort the repositories by sortorder
var sorted_repositories = [];
for (var i in this.options.repositories) {
sorted_repositories[i] = this.options.repositories[i];
}
sorted_repositories.sort(function(a,b){return a.sortorder-b.sortorder});
// extract one repository template and repeat it for all repositories available,
// set name and icon and assign callbacks
var reponode = this.fpnode.one('.fp-repo');
if (reponode) {
var list = reponode.get('parentNode');
list.removeChild(reponode);
for (i in sorted_repositories) {
var repository = sorted_repositories[i];
var node = reponode.cloneNode(true);
list.appendChild(node);
node.
set('id', 'fp-repo-'+client_id+'-'+repository.id).
on('click', function(e, repository_id) {
e.preventDefault();
this.set_preference('recentrepository', repository_id);
this.hide_header();
this.list({'repo_id':repository_id});
}, this /*handler running scope*/, repository.id/*second argument of handler*/);
node.one('.fp-repo-name').setContent(repository.name);
node.one('.fp-repo-icon').set('src', repository.icon);
if (i==0) {
node.addClass('first');
}
if (i==sorted_repositories.length-1) {
node.addClass('last');
}
if (i%2) {
node.addClass('even');
} else {
node.addClass('odd');
}
}
}
// display error if no repositories found
if (sorted_repositories.length==0) {
this.display_error(M.str.repository.norepositoriesavailable, 'norepositoriesavailable')
}
// display repository that was used last time
this.show_recent_repository();
},
parse_repository_options: function(data, appendtolist) {
if (appendtolist) {
if (data.list) {
if (!this.filelist) {
this.filelist = [];
}
for (var i in data.list) {
this.filelist[this.filelist.length] = data.list[i];
}
}
} else {
this.filelist = data.list?data.list:null;
this.lazyloading = {};
}
this.filepath = data.path?data.path:null;
this.objecttag = data.object?data.object:null;
this.active_repo = {};
this.active_repo.issearchresult = data.issearchresult ? true : false;
this.active_repo.dynload = data.dynload?data.dynload:false;
this.active_repo.pages = Number(data.pages?data.pages:null);
this.active_repo.page = Number(data.page?data.page:null);
this.active_repo.hasmorepages = (this.active_repo.pages && this.active_repo.page && (this.active_repo.page < this.active_repo.pages || this.active_repo.pages == -1))
this.active_repo.id = data.repo_id?data.repo_id:null;
this.active_repo.nosearch = (data.login || data.nosearch); // this is either login form or 'nosearch' attribute set
this.active_repo.norefresh = (data.login || data.norefresh); // this is either login form or 'norefresh' attribute set
this.active_repo.nologin = (data.login || data.nologin); // this is either login form or 'nologin' attribute is set
this.active_repo.logouttext = data.logouttext?data.logouttext:null;
this.active_repo.logouturl = (data.logouturl || '');
this.active_repo.message = (data.message || '');
this.active_repo.help = data.help?data.help:null;
this.active_repo.manage = data.manage?data.manage:null;
this.print_header();
},
print_login: function(data) {
this.parse_repository_options(data);
var client_id = this.options.client_id;
var repository_id = data.repo_id;
var l = this.logindata = data.login;
var loginurl = '';
var action = data['login_btn_action'] ? data['login_btn_action'] : 'login';
var form_id = 'fp-form-'+client_id;
var loginform_node = Y.Node.create(M.core_filepicker.templates.loginform);
loginform_node.one('form').set('id', form_id);
this.fpnode.one('.fp-content').setContent('').appendChild(loginform_node);
var templates = {
'popup' : loginform_node.one('.fp-login-popup'),
'textarea' : loginform_node.one('.fp-login-textarea'),
'select' : loginform_node.one('.fp-login-select'),
'text' : loginform_node.one('.fp-login-text'),
'radio' : loginform_node.one('.fp-login-radiogroup'),
'checkbox' : loginform_node.one('.fp-login-checkbox'),
'input' : loginform_node.one('.fp-login-input')
};
var container;
for (var i in templates) {
if (templates[i]) {
container = templates[i].get('parentNode');
container.removeChild(templates[i]);
}
}
for(var k in l) {
if (templates[l[k].type]) {
var node = templates[l[k].type].cloneNode(true);
} else {
node = templates['input'].cloneNode(true);
}
if (l[k].type == 'popup') {
// submit button
loginurl = l[k].url;
var popupbutton = node.one('button');
popupbutton.on('click', function(e){
M.core_filepicker.active_filepicker = this;
window.open(loginurl, 'repo_auth', 'location=0,status=0,width=500,height=300,scrollbars=yes');
e.preventDefault();
}, this);
loginform_node.one('form').on('keydown', function(e) {
if (e.keyCode == 13) {
popupbutton.simulate('click');
e.preventDefault();
}
}, this);
loginform_node.all('.fp-login-submit').remove();
action = 'popup';
} else if(l[k].type=='textarea') {
// textarea element
if (node.one('label')) {
node.one('label').set('for', l[k].id).setContent(l[k].label);
}
node.one('textarea').setAttrs({id:l[k].id, name:l[k].name});
} else if(l[k].type=='select') {
// select element
if (node.one('label')) {
node.one('label').set('for', l[k].id).setContent(l[k].label);
}
node.one('select').setAttrs({id:l[k].id, name:l[k].name}).setContent('');
for (i in l[k].options) {
node.one('select').appendChild(
Y.Node.create('<option/>').
set('value', l[k].options[i].value).
setContent(l[k].options[i].label));
}
} else if(l[k].type=='radio') {
// radio input element
node.all('label').setContent(l[k].label);
var list = l[k].value.split('|');
var labels = l[k].value_label.split('|');
var radionode = null;
for(var item in list) {
if (radionode == null) {
radionode = node.one('.fp-login-radio');
radionode.one('input').set('checked', 'checked');
} else {
var x = radionode.cloneNode(true);
radionode.insert(x, 'after');
radionode = x;
radionode.one('input').set('checked', '');
}
radionode.one('input').setAttrs({id:''+l[k].id+item, name:l[k].name,
type:l[k].type, value:list[item]});
radionode.all('label').setContent(labels[item]).set('for', ''+l[k].id+item)
}
if (radionode == null) {
node.one('.fp-login-radio').remove();
}
} else {
// input element
if (node.one('label')) { node.one('label').set('for', l[k].id).setContent(l[k].label) }
node.one('input').
set('type', l[k].type).
set('id', l[k].id).
set('name', l[k].name).
set('value', l[k].value?l[k].value:'')
}
container.appendChild(node);
}
// custom label text for submit button
if (data['login_btn_label']) {
loginform_node.all('.fp-login-submit').setContent(data['login_btn_label'])
}
// register button action for login and search
if (action == 'login' || action == 'search') {
loginform_node.one('.fp-login-submit').on('click', function(e){
e.preventDefault();
this.hide_header();
this.request({
'scope': this,
'action':(action == 'search') ? 'search' : 'signin',
'path': '',
'client_id': client_id,
'repository_id': repository_id,
'form': {id:form_id, upload:false, useDisabled:true},
'callback': this.display_response
}, true);
}, this);
}
// if 'Enter' is pressed in the form, simulate the button click
if (loginform_node.one('.fp-login-submit')) {
loginform_node.one('form').on('keydown', function(e) {
if (e.keyCode == 13) {
loginform_node.one('.fp-login-submit').simulate('click')
e.preventDefault();
}
}, this);
}
},
display_response: function(id, obj, args) {
var scope = args.scope
// highlight the current repository in repositories list
scope.fpnode.all('.fp-repo.active').removeClass('active');
scope.fpnode.all('#fp-repo-'+scope.options.client_id+'-'+obj.repo_id).addClass('active')
// add class repository_REPTYPE to the filepicker (for repository-specific styles)
for (var i in scope.options.repositories) {
scope.fpnode.removeClass('repository_'+scope.options.repositories[i].type)
}
if (obj.repo_id && scope.options.repositories[obj.repo_id]) {
scope.fpnode.addClass('repository_'+scope.options.repositories[obj.repo_id].type)
}
// display response
if (obj.login) {
scope.viewbar_set_enabled(false);
scope.print_login(obj);
} else if (obj.upload) {
scope.viewbar_set_enabled(false);
scope.parse_repository_options(obj);
scope.create_upload_form(obj);
} else if (obj.object) {
M.core_filepicker.active_filepicker = scope;
scope.viewbar_set_enabled(false);
scope.parse_repository_options(obj);
scope.create_object_container(obj.object);
} else if (obj.list) {
scope.viewbar_set_enabled(true);
scope.parse_repository_options(obj);
scope.view_files();
}
},
list: function(args) {
if (!args) {
args = {};
}
if (!args.repo_id) {
args.repo_id = this.active_repo.id;
}
if (!args.path) {
args.path = '';
}
this.currentpath = args.path;
this.request({
action: 'list',
client_id: this.options.client_id,
repository_id: args.repo_id,
path: args.path,
page: args.page,
scope: this,
callback: this.display_response
}, true);
},
populate_licenses_select: function(node) {
if (!node) {
return;
}
node.setContent('');
var licenses = this.options.licenses;
var recentlicense = this.get_preference('recentlicense');
if (recentlicense) {
this.options.defaultlicense=recentlicense;
}
for (var i in licenses) {
var option = Y.Node.create('<option/>').
set('selected', (this.options.defaultlicense==licenses[i].shortname)).
set('value', licenses[i].shortname).
setContent(licenses[i].fullname);
node.appendChild(option)
}
},
set_selected_license: function(node, value) {
var licenseset = false;
node.all('option').each(function(el) {
if (el.get('value')==value || el.getContent()==value) {
el.set('selected', true);
licenseset = true;
}
});
if (!licenseset) {
// we did not find the value in the list
var recentlicense = this.get_preference('recentlicense');
node.all('option[selected]').set('selected', false);
node.all('option[value='+recentlicense+']').set('selected', true);
}
},
create_object_container: function(data) {
var content = this.fpnode.one('.fp-content');
content.setContent('');
//var str = '<object data="'+data.src+'" type="'+data.type+'" width="98%" height="98%" id="container_object" class="fp-object-container mdl-align"></object>';
var container = Y.Node.create('<object/>').
setAttrs({data:data.src, type:data.type, id:'container_object'}).
addClass('fp-object-container');
content.setContent('').appendChild(container);
},
create_upload_form: function(data) {
var client_id = this.options.client_id;
var id = data.upload.id+'_'+client_id;
var content = this.fpnode.one('.fp-content');
var template_name = 'uploadform_'+this.options.repositories[data.repo_id].type;
var template = M.core_filepicker.templates[template_name] || M.core_filepicker.templates['uploadform'];
content.setContent(template);
content.all('.fp-file,.fp-saveas,.fp-setauthor,.fp-setlicense').each(function (node) {
node.all('label').set('for', node.one('input,select').generateID());
});
content.one('form').set('id', id);
content.one('.fp-file input').set('name', 'repo_upload_file');
if (data.upload.label && content.one('.fp-file label')) {
content.one('.fp-file label').setContent(data.upload.label);
}
content.one('.fp-saveas input').set('name', 'title');
content.one('.fp-setauthor input').setAttrs({name:'author', value:this.options.author});
content.one('.fp-setlicense select').set('name', 'license');
this.populate_licenses_select(content.one('.fp-setlicense select'))
// append hidden inputs to the upload form
content.one('form').appendChild(Y.Node.create('<input/>').
setAttrs({type:'hidden',name:'itemid',value:this.options.itemid}));
var types = this.options.accepted_types;
for (var i in types) {
content.one('form').appendChild(Y.Node.create('<input/>').
setAttrs({type:'hidden',name:'accepted_types[]',value:types[i]}));
}
var scope = this;
content.one('.fp-upload-btn').on('click', function(e) {
e.preventDefault();
var license = content.one('.fp-setlicense select');
this.set_preference('recentlicense', license.get('value'));
if (!content.one('.fp-file input').get('value')) {
scope.print_msg(M.str.repository.nofilesattached, 'error');
return false;
}
this.hide_header();
scope.request({
scope: scope,
action:'upload',
client_id: client_id,
params: {'savepath':scope.options.savepath},
repository_id: scope.active_repo.id,
form: {id: id, upload:true},
onerror: function(id, o, args) {
scope.create_upload_form(data);
},
callback: function(id, o, args) {
if (o.event == 'fileexists') {
scope.create_upload_form(data);
scope.process_existing_file(o);
return;
}
if (scope.options.editor_target&&scope.options.env=='editor') {
scope.options.editor_target.value=o.url;
scope.options.editor_target.onchange();
}
scope.hide();
o.client_id = client_id;
var formcallback_scope = args.scope.options.magicscope ? args.scope.options.magicscope : args.scope;
scope.options.formcallback.apply(formcallback_scope, [o]);
}
}, true);
}, this);
},
/** setting handlers and labels for elements in toolbar. Called once during the initial render of filepicker */
setup_toolbar: function() {
var client_id = this.options.client_id;
var toolbar = this.fpnode.one('.fp-toolbar');
toolbar.one('.fp-tb-logout').one('a,button').on('click', function(e) {
e.preventDefault();
if (!this.active_repo.nologin) {
this.hide_header();
this.request({
action:'logout',
client_id: this.options.client_id,
repository_id: this.active_repo.id,
path:'',
callback: this.display_response
}, true);
}
if (this.active_repo.logouturl) {
window.open(this.active_repo.logouturl, 'repo_auth', 'location=0,status=0,width=500,height=300,scrollbars=yes');
}
}, this);
toolbar.one('.fp-tb-refresh').one('a,button').on('click', function(e) {
e.preventDefault();
if (!this.active_repo.norefresh) {
this.list({ path: this.currentpath });
}
}, this);
toolbar.one('.fp-tb-search form').
set('method', 'POST').
set('id', 'fp-tb-search-'+client_id).
on('submit', function(e) {
e.preventDefault();
if (!this.active_repo.nosearch) {
this.request({
scope: this,
action:'search',
client_id: this.options.client_id,
repository_id: this.active_repo.id,
form: {id: 'fp-tb-search-'+client_id, upload:false, useDisabled:true},
callback: this.display_response
}, true);
}
}, this);
// it does not matter what kind of element is .fp-tb-manage, we create a dummy <a>
// element and use it to open url on click event
var managelnk = Y.Node.create('<a/>').
setAttrs({id:'fp-tb-manage-'+client_id+'-link', target:'_blank'}).
setStyle('display', 'none');
toolbar.append(managelnk);
toolbar.one('.fp-tb-manage').one('a,button').
on('click', function(e) {
e.preventDefault();
managelnk.simulate('click')
});
// same with .fp-tb-help
var helplnk = Y.Node.create('<a/>').
setAttrs({id:'fp-tb-help-'+client_id+'-link', target:'_blank'}).
setStyle('display', 'none');
toolbar.append(helplnk);
toolbar.one('.fp-tb-help').one('a,button').
on('click', function(e) {
e.preventDefault();
helplnk.simulate('click')
});
},
hide_header: function() {
if (this.fpnode.one('.fp-toolbar')) {
this.fpnode.one('.fp-toolbar').addClass('empty');
}
if (this.pathbar) {
this.pathbar.setContent('').addClass('empty');
}
},
print_header: function() {
var r = this.active_repo;
var scope = this;
var client_id = this.options.client_id;
this.hide_header();
this.print_path();
var toolbar = this.fpnode.one('.fp-toolbar');
if (!toolbar) { return; }
var enable_tb_control = function(node, enabled) {
if (!node) { return; }
node.addClassIf('disabled', !enabled).addClassIf('enabled', enabled)
if (enabled) {
toolbar.removeClass('empty');
}
}
// TODO 'back' permanently disabled for now. Note, flickr_public uses 'Logout' for it!
enable_tb_control(toolbar.one('.fp-tb-back'), false);
// search form
enable_tb_control(toolbar.one('.fp-tb-search'), !r.nosearch);
if(!r.nosearch) {
var searchform = toolbar.one('.fp-tb-search form');
searchform.setContent('');
this.request({
scope: this,
action:'searchform',
repository_id: this.active_repo.id,
callback: function(id, obj, args) {
if (obj.repo_id == scope.active_repo.id && obj.form) {
// if we did not jump to another repository meanwhile
searchform.setContent(obj.form);
// Highlight search text when user click for search.
var searchnode = searchform.one('input[name="s"]');
if (searchnode) {
searchnode.once('click', function(e) {
e.preventDefault();
this.select();
});
}
}
}
}, false);
}
// refresh button
// weather we use cache for this instance, this button will reload listing anyway
enable_tb_control(toolbar.one('.fp-tb-refresh'), !r.norefresh);
// login button
enable_tb_control(toolbar.one('.fp-tb-logout'), !r.nologin);
if (!r.nologin) {
var label = r.logouttext ? r.logouttext : M.str.repository.logout;
toolbar.one('.fp-tb-logout').one('a,button').setContent(label)
}
// manage url
enable_tb_control(toolbar.one('.fp-tb-manage'), r.manage);
Y.one('#fp-tb-manage-'+client_id+'-link').set('href', r.manage);
// help url
enable_tb_control(toolbar.one('.fp-tb-help'), r.help);
Y.one('#fp-tb-help-'+client_id+'-link').set('href', r.help);
// message
enable_tb_control(toolbar.one('.fp-tb-message'), r.message);
toolbar.one('.fp-tb-message').setContent(r.message);
},
print_path: function() {
if (!this.pathbar) {
return;
}
this.pathbar.setContent('').addClass('empty');
var p = this.filepath;
if (p && p.length!=0 && this.viewmode != 2) {
for(var i = 0; i < p.length; i++) {
var el = this.pathnode.cloneNode(true);
this.pathbar.appendChild(el);
if (i == 0) {
el.addClass('first');
}
if (i == p.length-1) {
el.addClass('last');
}
if (i%2) {
el.addClass('even');
} else {
el.addClass('odd');
}
el.all('.fp-path-folder-name').setContent(p[i].name);
el.on('click',
function(e, path) {
e.preventDefault();
this.list({'path':path});
},
this, p[i].path);
}
this.pathbar.removeClass('empty');
}
},
hide: function() {
this.selectui.hide();
if (this.process_dlg) {
this.process_dlg.hide();
}
if (this.msg_dlg) {
this.msg_dlg.hide();
}
this.mainui.hide();
},
show: function() {
if (this.fpnode) {
this.hide();
this.mainui.show();
this.show_recent_repository();
} else {
this.launch();
}
},
launch: function() {
this.render();
},
show_recent_repository: function() {
this.hide_header();
this.viewbar_set_enabled(false);
var repository_id = this.get_preference('recentrepository');
this.viewmode = this.get_preference('recentviewmode');
if (this.viewmode != 2 && this.viewmode != 3) {
this.viewmode = 1;
}
if (this.options.repositories[repository_id]) {
this.list({'repo_id':repository_id});
}
},
get_preference: function (name) {
if (this.options.userprefs[name]) {
return this.options.userprefs[name];
} else {
return false;
}
},
set_preference: function(name, value) {
if (this.options.userprefs[name] != value) {
M.util.set_user_preference('filepicker_' + name, value);
this.options.userprefs[name] = value;
}
}
});
var loading = Y.one('#filepicker-loading-'+options.client_id);
if (loading) {
loading.setStyle('display', 'none');
}
M.core_filepicker.instances[options.client_id] = new FilePickerHelper(options);
};