| [ Index ] |
PHP Cross Reference of Moodle 310 |
[Summary view] [Print] [Text view]
1 // YUI3 File Picker module for moodle 2 // Author: Dongsheng Cai <dongsheng@moodle.com> 3 4 /** 5 * 6 * File Picker UI 7 * ===== 8 * this.fpnode, contains reference to filepicker Node, non-empty if and only if rendered 9 * this.api, stores the URL to make ajax request 10 * this.mainui, YUI Panel 11 * this.selectnode, contains reference to select-file Node 12 * this.selectui, YUI Panel for selecting particular file 13 * this.msg_dlg, YUI Panel for error or info message 14 * this.process_dlg, YUI Panel for processing existing filename 15 * this.treeview, YUI Treeview 16 * this.viewmode, store current view mode 17 * this.pathbar, reference to the Node with path bar 18 * this.pathnode, a Node element representing one folder in a path bar (not attached anywhere, just used for template) 19 * this.currentpath, the current path in the repository (or last requested path) 20 * 21 * Filepicker options: 22 * ===== 23 * this.options.client_id, the instance id 24 * this.options.contextid 25 * this.options.itemid 26 * this.options.repositories, stores all repositories displayed in file picker 27 * this.options.formcallback 28 * 29 * Active repository options 30 * ===== 31 * this.active_repo.id 32 * this.active_repo.defaultreturntype 33 * this.active_repo.nosearch 34 * this.active_repo.norefresh 35 * this.active_repo.nologin 36 * this.active_repo.help 37 * this.active_repo.manage 38 * 39 * Server responses 40 * ===== 41 * this.filelist, cached filelist 42 * this.pages 43 * this.page 44 * this.filepath, current path (each element of the array is a part of the breadcrumb) 45 * this.logindata, cached login form 46 */ 47 48 YUI.add('moodle-core_filepicker', function(Y) { 49 /** help function to extract width/height style as a number, not as a string */ 50 Y.Node.prototype.getStylePx = function(attr) { 51 var style = this.getStyle(attr); 52 if (''+style == '0' || ''+style == '0px') { 53 return 0; 54 } 55 var matches = style.match(/^([\d\.]+)px$/) 56 if (matches && parseFloat(matches[1])) { 57 return parseFloat(matches[1]); 58 } 59 return null; 60 } 61 62 /** if condition is met, the class is added to the node, otherwise - removed */ 63 Y.Node.prototype.addClassIf = function(className, condition) { 64 if (condition) { 65 this.addClass(className); 66 } else { 67 this.removeClass(className); 68 } 69 return this; 70 } 71 72 /** sets the width(height) of the node considering existing minWidth(minHeight) */ 73 Y.Node.prototype.setStyleAdv = function(stylename, value) { 74 var stylenameCap = stylename.substr(0,1).toUpperCase() + stylename.substr(1, stylename.length-1).toLowerCase(); 75 this.setStyle(stylename, '' + Math.max(value, this.getStylePx('min'+stylenameCap)) + 'px') 76 return this; 77 } 78 79 /** set image source to src, if there is preview, remember it in lazyloading. 80 * If there is a preview and it was already loaded, use it. */ 81 Y.Node.prototype.setImgSrc = function(src, realsrc, lazyloading) { 82 if (realsrc) { 83 if (M.core_filepicker.loadedpreviews[realsrc]) { 84 this.set('src', realsrc).addClass('realpreview'); 85 return this; 86 } else { 87 if (!this.get('id')) { 88 this.generateID(); 89 } 90 lazyloading[this.get('id')] = realsrc; 91 } 92 } 93 this.set('src', src); 94 return this; 95 } 96 97 /** 98 * Replaces the image source with preview. If the image is inside the treeview, we need 99 * also to update the html property of corresponding YAHOO.widget.HTMLNode 100 * @param array lazyloading array containing associations of imgnodeid->realsrc 101 */ 102 Y.Node.prototype.setImgRealSrc = function(lazyloading) { 103 if (this.get('id') && lazyloading[this.get('id')]) { 104 var newsrc = lazyloading[this.get('id')]; 105 M.core_filepicker.loadedpreviews[newsrc] = true; 106 this.set('src', newsrc).addClass('realpreview'); 107 delete lazyloading[this.get('id')]; 108 var treenode = this.ancestor('.fp-treeview') 109 if (treenode && treenode.get('parentNode').treeview) { 110 treenode.get('parentNode').treeview.getRoot().refreshPreviews(this.get('id'), newsrc); 111 } 112 } 113 return this; 114 } 115 116 /** scan TreeView to find which node contains image with id=imgid and replace it's html 117 * with the new image source. */ 118 Y.YUI2.widget.Node.prototype.refreshPreviews = function(imgid, newsrc, regex) { 119 if (!regex) { 120 regex = new RegExp("<img\\s[^>]*id=\""+imgid+"\"[^>]*?(/?)>", "im"); 121 } 122 if (this.expanded || this.isLeaf) { 123 var html = this.getContentHtml(); 124 if (html && this.setHtml && regex.test(html)) { 125 var newhtml = this.html.replace(regex, "<img id=\""+imgid+"\" src=\""+newsrc+"\" class=\"realpreview\"$1>", html); 126 this.setHtml(newhtml); 127 return true; 128 } 129 if (!this.isLeaf && this.children) { 130 for(var c in this.children) { 131 if (this.children[c].refreshPreviews(imgid, newsrc, regex)) { 132 return true; 133 } 134 } 135 } 136 } 137 return false; 138 } 139 140 /** 141 * Displays a list of files (used by filepicker, filemanager) inside the Node 142 * 143 * @param array options 144 * viewmode : 1 - icons, 2 - tree, 3 - table 145 * appendonly : whether fileslist need to be appended instead of replacing the existing content 146 * filenode : Node element that contains template for displaying one file 147 * callback : On click callback. The element of the fileslist array will be passed as argument 148 * rightclickcallback : On right click callback (optional). 149 * callbackcontext : context where callbacks are executed 150 * sortable : whether content may be sortable (in table mode) 151 * dynload : allow dynamic load for tree view 152 * filepath : for pre-building of tree view - the path to the current directory in filepicker format 153 * treeview_dynload : callback to function to dynamically load the folder in tree view 154 * classnamecallback : callback to function that returns the class name for an element 155 * @param array fileslist array of files to show, each array element may have attributes: 156 * title or fullname : file name 157 * shorttitle (optional) : display file name 158 * thumbnail : url of image 159 * icon : url of icon image 160 * thumbnail_width : width of thumbnail, default 90 161 * thumbnail_height : height of thumbnail, default 90 162 * thumbnail_alt : TODO not needed! 163 * description or thumbnail_title : alt text 164 * @param array lazyloading : reference to the array with lazy loading images 165 */ 166 Y.Node.prototype.fp_display_filelist = function(options, fileslist, lazyloading) { 167 var viewmodeclassnames = {1:'fp-iconview', 2:'fp-treeview', 3:'fp-tableview'}; 168 var classname = viewmodeclassnames[options.viewmode]; 169 var scope = this; 170 /** return whether file is a folder (different attributes in FileManager and FilePicker) */ 171 var file_is_folder = function(node) { 172 if (node.children) {return true;} 173 if (node.type && node.type == 'folder') {return true;} 174 return false; 175 }; 176 /** return the name of the file (different attributes in FileManager and FilePicker) */ 177 var file_get_filename = function(node) { 178 return node.title ? node.title : node.fullname; 179 }; 180 /** return display name of the file (different attributes in FileManager and FilePicker) */ 181 var file_get_displayname = function(node) { 182 var displayname = node.shorttitle ? node.shorttitle : file_get_filename(node); 183 return Y.Escape.html(displayname); 184 }; 185 /** return file description (different attributes in FileManager and FilePicker) */ 186 var file_get_description = function(node) { 187 var description = ''; 188 if (node.description) { 189 description = node.description; 190 } else if (node.thumbnail_title) { 191 description = node.thumbnail_title; 192 } else { 193 description = file_get_filename(node); 194 } 195 return Y.Escape.html(description); 196 }; 197 /** help funciton for tree view */ 198 var build_tree = function(node, level) { 199 // prepare file name with icon 200 var el = Y.Node.create('<div/>'); 201 el.appendChild(options.filenode.cloneNode(true)); 202 203 el.one('.fp-filename').setContent(file_get_displayname(node)); 204 // TODO add tooltip with node.title or node.thumbnail_title 205 var tmpnodedata = {className:options.classnamecallback(node)}; 206 el.get('children').addClass(tmpnodedata.className); 207 if (node.icon) { 208 el.one('.fp-icon').appendChild(Y.Node.create('<img/>')); 209 el.one('.fp-icon img').setImgSrc(node.icon, node.realicon, lazyloading); 210 } 211 // create node 212 tmpnodedata.html = el.getContent(); 213 var tmpNode = new Y.YUI2.widget.HTMLNode(tmpnodedata, level, false); 214 if (node.dynamicLoadComplete) { 215 tmpNode.dynamicLoadComplete = true; 216 } 217 tmpNode.fileinfo = node; 218 tmpNode.isLeaf = !file_is_folder(node); 219 if (!tmpNode.isLeaf) { 220 if(node.expanded) { 221 tmpNode.expand(); 222 } 223 tmpNode.path = node.path ? node.path : (node.filepath ? node.filepath : ''); 224 for(var c in node.children) { 225 build_tree(node.children[c], tmpNode); 226 } 227 } 228 }; 229 /** initialize tree view */ 230 var initialize_tree_view = function() { 231 var parentid = scope.one('.'+classname).get('id'); 232 // TODO MDL-32736 use YUI3 gallery TreeView 233 scope.treeview = new Y.YUI2.widget.TreeView(parentid); 234 if (options.dynload) { 235 scope.treeview.setDynamicLoad(Y.bind(options.treeview_dynload, options.callbackcontext), 1); 236 } 237 scope.treeview.singleNodeHighlight = true; 238 if (options.filepath && options.filepath.length) { 239 // we just jumped from icon/details view, we need to show all parents 240 // we extract as much information as possible from filepath and filelist 241 // and send additional requests to retrieve siblings for parent folders 242 var mytree = {}; 243 var mytreeel = null; 244 for (var i in options.filepath) { 245 if (mytreeel == null) { 246 mytreeel = mytree; 247 } else { 248 mytreeel.children = [{}]; 249 mytreeel = mytreeel.children[0]; 250 } 251 var pathelement = options.filepath[i]; 252 mytreeel.path = pathelement.path; 253 mytreeel.title = pathelement.name; 254 mytreeel.icon = pathelement.icon; 255 mytreeel.dynamicLoadComplete = true; // we will call it manually 256 mytreeel.expanded = true; 257 } 258 mytreeel.children = fileslist; 259 build_tree(mytree, scope.treeview.getRoot()); 260 // manually call dynload for parent elements in the tree so we can load other siblings 261 if (options.dynload) { 262 var root = scope.treeview.getRoot(); 263 // Whether search results are currently displayed in the active repository in the filepicker. 264 // We do not want to load siblings of parent elements when displaying search tree results. 265 var isSearchResult = typeof options.callbackcontext.active_repo !== 'undefined' && 266 options.callbackcontext.active_repo.issearchresult; 267 while (root && root.children && root.children.length) { 268 root = root.children[0]; 269 if (root.path == mytreeel.path) { 270 root.origpath = options.filepath; 271 root.origlist = fileslist; 272 } else if (!root.isLeaf && root.expanded && !isSearchResult) { 273 Y.bind(options.treeview_dynload, options.callbackcontext)(root, null); 274 } 275 } 276 } 277 } else { 278 // there is no path information, just display all elements as a list, without hierarchy 279 for(k in fileslist) { 280 build_tree(fileslist[k], scope.treeview.getRoot()); 281 } 282 } 283 scope.treeview.subscribe('clickEvent', function(e){ 284 e.node.highlight(false); 285 var callback = options.callback; 286 if (options.rightclickcallback && e.event.target && 287 Y.Node(e.event.target).ancestor('.fp-treeview .fp-contextmenu', true)) { 288 callback = options.rightclickcallback; 289 } 290 Y.bind(callback, options.callbackcontext)(e, e.node.fileinfo); 291 Y.YUI2.util.Event.stopEvent(e.event) 292 }); 293 // Simulate click on file not folder. 294 scope.treeview.subscribe('enterKeyPressed', function(node) { 295 if (node.children.length === 0) { 296 Y.one(node.getContentEl()).one('a').simulate('click'); 297 } 298 }); 299 // TODO MDL-32736 support right click 300 /*if (options.rightclickcallback) { 301 scope.treeview.subscribe('dblClickEvent', function(e){ 302 e.node.highlight(false); 303 Y.bind(options.rightclickcallback, options.callbackcontext)(e, e.node.fileinfo); 304 }); 305 }*/ 306 scope.treeview.draw(); 307 }; 308 /** formatting function for table view */ 309 var formatValue = function (o){ 310 if (o.data[''+o.column.key+'_f_s']) {return o.data[''+o.column.key+'_f_s'];} 311 else if (o.data[''+o.column.key+'_f']) {return o.data[''+o.column.key+'_f'];} 312 else if (o.value) {return o.value;} 313 else {return '';} 314 }; 315 /** formatting function for table view */ 316 var formatTitle = function(o) { 317 var el = Y.Node.create('<div/>'); 318 el.appendChild(options.filenode.cloneNode(true)); // TODO not node but string! 319 el.get('children').addClass(o.data['classname']); 320 el.one('.fp-filename').setContent(o.value); 321 if (o.data['icon']) { 322 el.one('.fp-icon').appendChild(Y.Node.create('<img/>')); 323 el.one('.fp-icon img').setImgSrc(o.data['icon'], o.data['realicon'], lazyloading); 324 } 325 if (options.rightclickcallback) { 326 el.get('children').addClass('fp-hascontextmenu'); 327 } 328 // TODO add tooltip with o.data['title'] (o.value) or o.data['thumbnail_title'] 329 return el.getContent(); 330 } 331 332 /** 333 * Generate slave checkboxes based on toggleall's specification 334 * @param {object} o An object reprsenting the record for the current row. 335 * @return {html} The checkbox html 336 */ 337 var formatCheckbox = function(o) { 338 var el = Y.Node.create('<div/>'); 339 340 var checkbox = Y.Node.create('<input/>') 341 .setAttribute('type', 'checkbox') 342 .setAttribute('data-fieldtype', 'checkbox') 343 .setAttribute('data-fullname', o.data.fullname) 344 .setAttribute('data-action', 'toggle') 345 .setAttribute('data-toggle', 'slave') 346 .setAttribute('data-togglegroup', 'file-selections') 347 .setAttribute('data-toggle-selectall', M.util.get_string('selectall', 'moodle')) 348 .setAttribute('data-toggle-deselectall', M.util.get_string('deselectall', 'moodle')); 349 350 var checkboxLabel = Y.Node.create('<label>') 351 .setHTML("Select file '" + o.data.fullname + "'") 352 .addClass('sr-only') 353 .setAttrs({ 354 for: checkbox.generateID(), 355 }); 356 357 el.appendChild(checkbox); 358 el.appendChild(checkboxLabel); 359 return el.getContent(); 360 }; 361 /** sorting function for table view */ 362 var sortFoldersFirst = function(a, b, desc) { 363 if (a.get('isfolder') && !b.get('isfolder')) { 364 return -1; 365 } 366 if (!a.get('isfolder') && b.get('isfolder')) { 367 return 1; 368 } 369 var aa = a.get(this.key), bb = b.get(this.key), dir = desc ? -1 : 1; 370 return (aa > bb) ? dir : ((aa < bb) ? -dir : 0); 371 } 372 /** initialize table view */ 373 var initialize_table_view = function() { 374 var cols = [ 375 {key: "displayname", label: M.util.get_string('name', 'moodle'), allowHTML: true, formatter: formatTitle, 376 sortable: true, sortFn: sortFoldersFirst}, 377 {key: "datemodified", label: M.util.get_string('lastmodified', 'moodle'), allowHTML: true, formatter: formatValue, 378 sortable: true, sortFn: sortFoldersFirst}, 379 {key: "size", label: M.util.get_string('size', 'repository'), allowHTML: true, formatter: formatValue, 380 sortable: true, sortFn: sortFoldersFirst}, 381 {key: "mimetype", label: M.util.get_string('type', 'repository'), allowHTML: true, 382 sortable: true, sortFn: sortFoldersFirst} 383 ]; 384 385 // Generate a checkbox based on toggleall's specification 386 var div = Y.Node.create('<div/>'); 387 var checkbox = Y.Node.create('<input/>') 388 .setAttribute('type', 'checkbox') 389 // .setAttribute('title', M.util.get_string('selectallornone', 'form')) 390 .setAttribute('data-action', 'toggle') 391 .setAttribute('data-toggle', 'master') 392 .setAttribute('data-togglegroup', 'file-selections'); 393 394 var checkboxLabel = Y.Node.create('<label>') 395 .setHTML(M.util.get_string('selectallornone', 'form')) 396 .addClass('sr-only') 397 .setAttrs({ 398 for: checkbox.generateID(), 399 }); 400 401 div.appendChild(checkboxLabel); 402 div.appendChild(checkbox); 403 404 // Define the selector for the click event handler. 405 var clickEventSelector = 'tr'; 406 // Enable the selectable checkboxes 407 if (options.disablecheckboxes != undefined && !options.disablecheckboxes) { 408 clickEventSelector = 'tr td:not(:first-child)'; 409 cols.unshift({ 410 key: "", 411 label: div.getContent(), 412 allowHTML: true, 413 formatter: formatCheckbox, 414 sortable: false 415 }); 416 } 417 scope.tableview = new Y.DataTable({columns: cols, data: fileslist}); 418 scope.tableview.delegate('click', function (e, tableview) { 419 var record = tableview.getRecord(e.currentTarget.get('id')); 420 if (record) { 421 var callback = options.callback; 422 if (options.rightclickcallback && e.target.ancestor('.fp-tableview .fp-contextmenu', true)) { 423 callback = options.rightclickcallback; 424 } 425 Y.bind(callback, this)(e, record.getAttrs()); 426 } 427 }, clickEventSelector, options.callbackcontext, scope.tableview); 428 429 if (options.rightclickcallback) { 430 scope.tableview.delegate('contextmenu', function (e, tableview) { 431 var record = tableview.getRecord(e.currentTarget.get('id')); 432 if (record) { Y.bind(options.rightclickcallback, this)(e, record.getAttrs()); } 433 }, 'tr', options.callbackcontext, scope.tableview); 434 } 435 } 436 /** append items in table view mode */ 437 var append_files_table = function() { 438 if (options.appendonly) { 439 fileslist.forEach(function(el) { 440 this.tableview.data.add(el); 441 },scope); 442 } 443 scope.tableview.render(scope.one('.'+classname)); 444 scope.tableview.sortable = options.sortable ? true : false; 445 }; 446 /** append items in tree view mode */ 447 var append_files_tree = function() { 448 if (options.appendonly) { 449 var parentnode = scope.treeview.getRoot(); 450 if (scope.treeview.getHighlightedNode()) { 451 parentnode = scope.treeview.getHighlightedNode(); 452 if (parentnode.isLeaf) {parentnode = parentnode.parent;} 453 } 454 for (var k in fileslist) { 455 build_tree(fileslist[k], parentnode); 456 } 457 scope.treeview.draw(); 458 } else { 459 // otherwise files were already added in initialize_tree_view() 460 } 461 } 462 /** append items in icon view mode */ 463 var append_files_icons = function() { 464 parent = scope.one('.'+classname); 465 for (var k in fileslist) { 466 var node = fileslist[k]; 467 var element = options.filenode.cloneNode(true); 468 parent.appendChild(element); 469 element.addClass(options.classnamecallback(node)); 470 var filenamediv = element.one('.fp-filename'); 471 filenamediv.setContent(file_get_displayname(node)); 472 var imgdiv = element.one('.fp-thumbnail'), width, height, src; 473 if (node.thumbnail) { 474 width = node.thumbnail_width ? node.thumbnail_width : 90; 475 height = node.thumbnail_height ? node.thumbnail_height : 90; 476 src = node.thumbnail; 477 } else { 478 width = 16; 479 height = 16; 480 src = node.icon; 481 } 482 filenamediv.setStyleAdv('width', width); 483 imgdiv.setStyleAdv('width', width).setStyleAdv('height', height); 484 var img = Y.Node.create('<img/>').setAttrs({ 485 title: file_get_description(node), 486 alt: Y.Escape.html(node.thumbnail_alt ? node.thumbnail_alt : file_get_filename(node))}). 487 setStyle('maxWidth', ''+width+'px'). 488 setStyle('maxHeight', ''+height+'px'); 489 img.setImgSrc(src, node.realthumbnail, lazyloading); 490 imgdiv.appendChild(img); 491 element.on('click', function(e, nd) { 492 if (options.rightclickcallback && e.target.ancestor('.fp-iconview .fp-contextmenu', true)) { 493 Y.bind(options.rightclickcallback, this)(e, nd); 494 } else { 495 Y.bind(options.callback, this)(e, nd); 496 } 497 }, options.callbackcontext, node); 498 if (options.rightclickcallback) { 499 element.on('contextmenu', options.rightclickcallback, options.callbackcontext, node); 500 } 501 } 502 } 503 504 // Notify the user if any of the files has a problem status. 505 var problemFiles = []; 506 fileslist.forEach(function(file) { 507 if (!file_is_folder(file) && file.hasOwnProperty('status') && file.status != 0) { 508 problemFiles.push(file); 509 } 510 }); 511 if (problemFiles.length > 0) { 512 require(["core/notification", "core/str"], function(Notification, Str) { 513 problemFiles.forEach(function(problemFile) { 514 Str.get_string('storedfilecannotreadfile', 'error', problemFile.fullname).then(function(string) { 515 Notification.addNotification({ 516 message: string, 517 type: "error" 518 }); 519 return; 520 }).catch(Notification.exception); 521 }); 522 }); 523 } 524 525 // If table view, need some additional properties 526 // before passing fileslist to the YUI tableview 527 if (options.viewmode == 3) { 528 fileslist.forEach(function(el) { 529 el.displayname = file_get_displayname(el); 530 el.isfolder = file_is_folder(el); 531 el.classname = options.classnamecallback(el); 532 }, scope); 533 } 534 535 // initialize files view 536 if (!options.appendonly) { 537 var parent = Y.Node.create('<div/>').addClass(classname); 538 this.setContent('').appendChild(parent); 539 parent.generateID(); 540 if (options.viewmode == 2) { 541 initialize_tree_view(); 542 } else if (options.viewmode == 3) { 543 initialize_table_view(); 544 } else { 545 // nothing to initialize for icon view 546 } 547 } 548 549 // append files to the list 550 if (options.viewmode == 2) { 551 append_files_tree(); 552 } else if (options.viewmode == 3) { 553 append_files_table(); 554 } else { 555 append_files_icons(); 556 } 557 558 } 559 }, '@VERSION@', { 560 requires:['base', 'node', 'yui2-treeview', 'panel', 'cookie', 'datatable', 'datatable-sort'] 561 }); 562 563 M.core_filepicker = M.core_filepicker || {}; 564 565 /** 566 * instances of file pickers used on page 567 */ 568 M.core_filepicker.instances = M.core_filepicker.instances || {}; 569 M.core_filepicker.active_filepicker = null; 570 571 /** 572 * HTML Templates to use in FilePicker 573 */ 574 M.core_filepicker.templates = M.core_filepicker.templates || {}; 575 576 /** 577 * Array of image sources for real previews (realicon or realthumbnail) that are already loaded 578 */ 579 M.core_filepicker.loadedpreviews = M.core_filepicker.loadedpreviews || {}; 580 581 /** 582 * Set selected file info 583 * 584 * @param object file info 585 */ 586 M.core_filepicker.select_file = function(file) { 587 M.core_filepicker.active_filepicker.select_file(file); 588 } 589 590 /** 591 * Init and show file picker 592 */ 593 M.core_filepicker.show = function(Y, options) { 594 if (!M.core_filepicker.instances[options.client_id]) { 595 M.core_filepicker.init(Y, options); 596 } 597 M.core_filepicker.instances[options.client_id].options.formcallback = options.formcallback; 598 M.core_filepicker.instances[options.client_id].show(); 599 }; 600 601 M.core_filepicker.set_templates = function(Y, templates) { 602 for (var templid in templates) { 603 M.core_filepicker.templates[templid] = templates[templid]; 604 } 605 } 606 607 /** 608 * Add new file picker to current instances 609 */ 610 M.core_filepicker.init = function(Y, options) { 611 var FilePickerHelper = function(options) { 612 FilePickerHelper.superclass.constructor.apply(this, arguments); 613 }; 614 615 FilePickerHelper.NAME = "FilePickerHelper"; 616 FilePickerHelper.ATTRS = { 617 options: {}, 618 lang: {} 619 }; 620 621 Y.extend(FilePickerHelper, Y.Base, { 622 api: M.cfg.wwwroot+'/repository/repository_ajax.php', 623 cached_responses: {}, 624 waitinterval : null, // When the loading template is being displayed and its animation is running this will be an interval instance. 625 initializer: function(options) { 626 this.options = options; 627 if (!this.options.savepath) { 628 this.options.savepath = '/'; 629 } 630 }, 631 632 destructor: function() { 633 }, 634 635 request: function(args, redraw) { 636 var api = (args.api ? args.api : this.api) + '?action='+args.action; 637 var params = {}; 638 var scope = args['scope'] ? args['scope'] : this; 639 params['repo_id']=args.repository_id; 640 params['p'] = args.path?args.path:''; 641 params['page'] = args.page?args.page:''; 642 params['env']=this.options.env; 643 // the form element only accept certain file types 644 params['accepted_types']=this.options.accepted_types; 645 params['sesskey'] = M.cfg.sesskey; 646 params['client_id'] = args.client_id; 647 params['itemid'] = this.options.itemid?this.options.itemid:0; 648 params['maxbytes'] = this.options.maxbytes?this.options.maxbytes:-1; 649 // The unlimited value of areamaxbytes is -1, it is defined by FILE_AREA_MAX_BYTES_UNLIMITED. 650 params['areamaxbytes'] = this.options.areamaxbytes ? this.options.areamaxbytes : -1; 651 if (this.options.context && this.options.context.id) { 652 params['ctx_id'] = this.options.context.id; 653 } 654 if (args['params']) { 655 for (i in args['params']) { 656 params[i] = args['params'][i]; 657 } 658 } 659 if (args.action == 'upload') { 660 var list = []; 661 for(var k in params) { 662 var value = params[k]; 663 if(value instanceof Array) { 664 for(var i in value) { 665 list.push(k+'[]='+value[i]); 666 } 667 } else { 668 list.push(k+'='+value); 669 } 670 } 671 params = list.join('&'); 672 } else { 673 params = build_querystring(params); 674 } 675 var cfg = { 676 method: 'POST', 677 on: { 678 complete: function(id,o,p) { 679 var data = null; 680 try { 681 data = Y.JSON.parse(o.responseText); 682 } catch(e) { 683 if (o && o.status && o.status > 0) { 684 Y.use('moodle-core-notification-exception', function() { 685 return new M.core.exception(e); 686 }); 687 return; 688 } 689 } 690 // error checking 691 if (data && data.error) { 692 Y.use('moodle-core-notification-ajaxexception', function () { 693 return new M.core.ajaxException(data); 694 }); 695 this.fpnode.one('.fp-content').setContent(''); 696 return; 697 } else { 698 if (data.msg) { 699 scope.print_msg(data.msg, 'info'); 700 } 701 // cache result if applicable 702 if (args.action != 'upload' && data.allowcaching) { 703 scope.cached_responses[params] = data; 704 } 705 // invoke callback 706 args.callback(id,data,p); 707 } 708 } 709 }, 710 arguments: { 711 scope: scope 712 }, 713 headers: { 714 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 715 }, 716 data: params, 717 context: this 718 }; 719 if (args.form) { 720 cfg.form = args.form; 721 } 722 // check if result of the same request has been already cached. If not, request it 723 // (never applicable in case of form submission and/or upload action): 724 if (!args.form && args.action != 'upload' && scope.cached_responses[params]) { 725 args.callback(null, scope.cached_responses[params], {scope: scope}) 726 } else { 727 Y.io(api, cfg); 728 if (redraw) { 729 this.wait(); 730 } 731 } 732 }, 733 /** displays the dialog and processes rename/overwrite if there is a file with the same name in the same filearea*/ 734 process_existing_file: function(data) { 735 var scope = this; 736 var handleOverwrite = function(e) { 737 // overwrite 738 e.preventDefault(); 739 var data = this.process_dlg.dialogdata; 740 var params = {} 741 params['existingfilename'] = data.existingfile.filename; 742 params['existingfilepath'] = data.existingfile.filepath; 743 params['newfilename'] = data.newfile.filename; 744 params['newfilepath'] = data.newfile.filepath; 745 this.hide_header(); 746 this.request({ 747 'params': params, 748 'scope': this, 749 'action':'overwrite', 750 'path': '', 751 'client_id': this.options.client_id, 752 'repository_id': this.active_repo.id, 753 'callback': function(id, o, args) { 754 scope.hide(); 755 // Add an arbitrary parameter to the URL to force browsers to re-load the new image even 756 // if the file name has not changed. 757 var urlimage = data.existingfile.url + "?time=" + (new Date()).getTime(); 758 if (scope.options.editor_target && scope.options.env == 'editor') { 759 // editor needs to update url 760 scope.options.editor_target.value = urlimage; 761 scope.options.editor_target.dispatchEvent(new Event('change'), {'bubbles': true}); 762 } 763 var fileinfo = {'client_id':scope.options.client_id, 764 'url': urlimage, 765 'file': data.existingfile.filename}; 766 var formcallback_scope = scope.options.magicscope ? scope.options.magicscope : scope; 767 scope.options.formcallback.apply(formcallback_scope, [fileinfo]); 768 } 769 }, true); 770 } 771 var handleRename = function(e) { 772 // inserts file with the new name 773 e.preventDefault(); 774 var scope = this; 775 var data = this.process_dlg.dialogdata; 776 if (scope.options.editor_target && scope.options.env == 'editor') { 777 scope.options.editor_target.value = data.newfile.url; 778 scope.options.editor_target.dispatchEvent(new Event('change'), {'bubbles': true}); 779 } 780 scope.hide(); 781 var formcallback_scope = scope.options.magicscope ? scope.options.magicscope : scope; 782 var fileinfo = {'client_id':scope.options.client_id, 783 'url':data.newfile.url, 784 'file':data.newfile.filename}; 785 scope.options.formcallback.apply(formcallback_scope, [fileinfo]); 786 } 787 var handleCancel = function(e) { 788 // Delete tmp file 789 e.preventDefault(); 790 var params = {}; 791 params['newfilename'] = this.process_dlg.dialogdata.newfile.filename; 792 params['newfilepath'] = this.process_dlg.dialogdata.newfile.filepath; 793 this.request({ 794 'params': params, 795 'scope': this, 796 'action':'deletetmpfile', 797 'path': '', 798 'client_id': this.options.client_id, 799 'repository_id': this.active_repo.id, 800 'callback': function(id, o, args) { 801 // let it be in background, from user point of view nothing is happenning 802 } 803 }, false); 804 this.process_dlg.hide(); 805 this.selectui.hide(); 806 } 807 if (!this.process_dlg) { 808 this.process_dlg_node = Y.Node.create(M.core_filepicker.templates.processexistingfile); 809 var node = this.process_dlg_node; 810 node.generateID(); 811 this.process_dlg = new M.core.dialogue({ 812 draggable : true, 813 bodyContent : node, 814 headerContent: M.util.get_string('fileexistsdialogheader', 'repository'), 815 centered : true, 816 modal : true, 817 visible : false, 818 zIndex : this.options.zIndex 819 }); 820 node.one('.fp-dlg-butoverwrite').on('click', handleOverwrite, this); 821 node.one('.fp-dlg-butrename').on('click', handleRename, this); 822 node.one('.fp-dlg-butcancel').on('click', handleCancel, this); 823 if (this.options.env == 'editor') { 824 node.one('.fp-dlg-text').setContent(M.util.get_string('fileexistsdialog_editor', 'repository')); 825 } else { 826 node.one('.fp-dlg-text').setContent(M.util.get_string('fileexistsdialog_filemanager', 'repository')); 827 } 828 } 829 this.selectnode.removeClass('loading'); 830 this.process_dlg.dialogdata = data; 831 this.process_dlg_node.one('.fp-dlg-butrename').setContent(M.util.get_string('renameto', 'repository', data.newfile.filename)); 832 this.process_dlg.show(); 833 }, 834 /** displays error instead of filepicker contents */ 835 display_error: function(errortext, errorcode) { 836 this.fpnode.one('.fp-content').setContent(M.core_filepicker.templates.error); 837 this.fpnode.one('.fp-content .fp-error'). 838 addClass(errorcode). 839 setContent(Y.Escape.html(errortext)); 840 }, 841 /** displays message in a popup */ 842 print_msg: function(msg, type) { 843 var header = M.util.get_string('error', 'moodle'); 844 if (type != 'error') { 845 type = 'info'; // one of only two types excepted 846 header = M.util.get_string('info', 'moodle'); 847 } 848 if (!this.msg_dlg) { 849 this.msg_dlg_node = Y.Node.create(M.core_filepicker.templates.message); 850 this.msg_dlg_node.generateID(); 851 852 this.msg_dlg = new M.core.dialogue({ 853 draggable : true, 854 bodyContent : this.msg_dlg_node, 855 centered : true, 856 modal : true, 857 visible : false, 858 zIndex : this.options.zIndex 859 }); 860 this.msg_dlg_node.one('.fp-msg-butok').on('click', function(e) { 861 e.preventDefault(); 862 this.msg_dlg.hide(); 863 }, this); 864 } 865 866 this.msg_dlg.set('headerContent', header); 867 this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type) 868 this.msg_dlg_node.one('.fp-msg-text').setContent(Y.Escape.html(msg)); 869 this.msg_dlg.show(); 870 }, 871 view_files: function(appenditems) { 872 this.viewbar_set_enabled(true); 873 this.print_path(); 874 /*if ((appenditems == null) && (!this.filelist || !this.filelist.length) && !this.active_repo.hasmorepages) { 875 // TODO do it via classes and adjust for each view mode! 876 // If there are no items and no next page, just display status message and quit 877 this.display_error(M.util.get_string('nofilesavailable', 'repository'), 'nofilesavailable'); 878 return; 879 }*/ 880 if (this.viewmode == 2) { 881 this.view_as_list(appenditems); 882 } else if (this.viewmode == 3) { 883 this.view_as_table(appenditems); 884 } else { 885 this.view_as_icons(appenditems); 886 } 887 this.fpnode.one('.fp-content').setAttribute('tabindex', '0'); 888 this.fpnode.one('.fp-content').focus(); 889 // display/hide the link for requesting next page 890 if (!appenditems && this.active_repo.hasmorepages) { 891 if (!this.fpnode.one('.fp-content .fp-nextpage')) { 892 this.fpnode.one('.fp-content').append(M.core_filepicker.templates.nextpage); 893 } 894 this.fpnode.one('.fp-content .fp-nextpage').one('a,button').on('click', function(e) { 895 e.preventDefault(); 896 this.fpnode.one('.fp-content .fp-nextpage').addClass('loading'); 897 this.request_next_page(); 898 }, this); 899 } 900 if (!this.active_repo.hasmorepages && this.fpnode.one('.fp-content .fp-nextpage')) { 901 this.fpnode.one('.fp-content .fp-nextpage').remove(); 902 } 903 if (this.fpnode.one('.fp-content .fp-nextpage')) { 904 this.fpnode.one('.fp-content .fp-nextpage').removeClass('loading'); 905 } 906 this.content_scrolled(); 907 }, 908 content_scrolled: function(e) { 909 setTimeout(Y.bind(function() { 910 if (this.processingimages) { 911 return; 912 } 913 this.processingimages = true; 914 var scope = this, 915 fpcontent = this.fpnode.one('.fp-content'), 916 fpcontenty = fpcontent.getY(), 917 fpcontentheight = fpcontent.getStylePx('height'), 918 nextpage = fpcontent.one('.fp-nextpage'), 919 is_node_visible = function(node) { 920 var offset = node.getY()-fpcontenty; 921 if (offset <= fpcontentheight && (offset >=0 || offset+node.getStylePx('height')>=0)) { 922 return true; 923 } 924 return false; 925 }; 926 // automatically load next page when 'more' link becomes visible 927 if (nextpage && !nextpage.hasClass('loading') && is_node_visible(nextpage)) { 928 nextpage.one('a,button').simulate('click'); 929 } 930 // replace src for visible images that need to be lazy-loaded 931 if (scope.lazyloading) { 932 fpcontent.all('img').each( function(node) { 933 if (node.get('id') && scope.lazyloading[node.get('id')] && is_node_visible(node)) { 934 node.setImgRealSrc(scope.lazyloading); 935 } 936 }); 937 } 938 this.processingimages = false; 939 }, this), 200) 940 }, 941 treeview_dynload: function(node, cb) { 942 var retrieved_children = {}; 943 if (node.children) { 944 for (var i in node.children) { 945 retrieved_children[node.children[i].path] = node.children[i]; 946 } 947 } 948 this.request({ 949 action:'list', 950 client_id: this.options.client_id, 951 repository_id: this.active_repo.id, 952 path:node.path?node.path:'', 953 page:node.page?args.page:'', 954 scope:this, 955 callback: function(id, obj, args) { 956 var list = obj.list; 957 var scope = args.scope; 958 // check that user did not leave the view mode before recieving this response 959 if (!(scope.active_repo.id == obj.repo_id && scope.viewmode == 2 && node && node.getChildrenEl())) { 960 return; 961 } 962 if (cb != null) { // (in manual mode do not update current path) 963 scope.viewbar_set_enabled(true); 964 scope.parse_repository_options(obj); 965 } 966 node.highlight(false); 967 node.origlist = obj.list ? obj.list : null; 968 node.origpath = obj.path ? obj.path : null; 969 node.children = []; 970 for(k in list) { 971 if (list[k].children && retrieved_children[list[k].path]) { 972 // if this child is a folder and has already been retrieved 973 node.children[node.children.length] = retrieved_children[list[k].path]; 974 } else { 975 // append new file to the list 976 scope.view_as_list([list[k]]); 977 } 978 } 979 if (cb == null) { 980 node.refresh(); 981 } else { 982 // invoke callback requested by TreeView component 983 cb(); 984 } 985 scope.content_scrolled(); 986 } 987 }, false); 988 }, 989 classnamecallback : function(node) { 990 var classname = ''; 991 if (node.children) { 992 classname = classname + ' fp-folder'; 993 } 994 if (node.isref) { 995 classname = classname + ' fp-isreference'; 996 } 997 if (node.iscontrolledlink) { 998 classname = classname + ' fp-iscontrolledlink'; 999 } 1000 if (node.refcount) { 1001 classname = classname + ' fp-hasreferences'; 1002 } 1003 if (node.originalmissing) { 1004 classname = classname + ' fp-originalmissing'; 1005 } 1006 return Y.Lang.trim(classname); 1007 }, 1008 /** displays list of files in tree (list) view mode. If param appenditems is specified, 1009 * appends those items to the end of the list. Otherwise (default behaviour) 1010 * clears the contents and displays the items from this.filelist */ 1011 view_as_list: function(appenditems) { 1012 var list = (appenditems != null) ? appenditems : this.filelist; 1013 this.viewmode = 2; 1014 if (!this.filelist || this.filelist.length==0 && (!this.filepath || !this.filepath.length)) { 1015 this.display_error(M.util.get_string('nofilesavailable', 'repository'), 'nofilesavailable'); 1016 return; 1017 } 1018 1019 var element_template = Y.Node.create(M.core_filepicker.templates.listfilename); 1020 var options = { 1021 viewmode : this.viewmode, 1022 appendonly : (appenditems != null), 1023 filenode : element_template, 1024 callbackcontext : this, 1025 callback : function(e, node) { 1026 // TODO MDL-32736 e is not an event here but an object with properties 'event' and 'node' 1027 if (!node.children) { 1028 if (e.node.parent && e.node.parent.origpath) { 1029 // set the current path 1030 this.filepath = e.node.parent.origpath; 1031 this.filelist = e.node.parent.origlist; 1032 this.print_path(); 1033 } 1034 this.select_file(node); 1035 } else { 1036 // save current path and filelist (in case we want to jump to other viewmode) 1037 this.filepath = e.node.origpath; 1038 this.filelist = e.node.origlist; 1039 this.currentpath = e.node.path; 1040 this.print_path(); 1041 this.content_scrolled(); 1042 } 1043 }, 1044 classnamecallback : this.classnamecallback, 1045 dynload : this.active_repo.dynload, 1046 filepath : this.filepath, 1047 treeview_dynload : this.treeview_dynload 1048 }; 1049 this.fpnode.one('.fp-content').fp_display_filelist(options, list, this.lazyloading); 1050 }, 1051 /** displays list of files in icon view mode. If param appenditems is specified, 1052 * appends those items to the end of the list. Otherwise (default behaviour) 1053 * clears the contents and displays the items from this.filelist */ 1054 view_as_icons: function(appenditems) { 1055 this.viewmode = 1; 1056 var list = (appenditems != null) ? appenditems : this.filelist; 1057 var element_template = Y.Node.create(M.core_filepicker.templates.iconfilename); 1058 if ((appenditems == null) && (!this.filelist || !this.filelist.length)) { 1059 this.display_error(M.util.get_string('nofilesavailable', 'repository'), 'nofilesavailable'); 1060 return; 1061 } 1062 var options = { 1063 viewmode : this.viewmode, 1064 appendonly : (appenditems != null), 1065 filenode : element_template, 1066 callbackcontext : this, 1067 callback : function(e, node) { 1068 if (e.preventDefault) { 1069 e.preventDefault(); 1070 } 1071 if(node.children) { 1072 if (this.active_repo.dynload) { 1073 this.list({'path':node.path}); 1074 } else { 1075 this.filelist = node.children; 1076 this.view_files(); 1077 } 1078 } else { 1079 this.select_file(node); 1080 } 1081 }, 1082 classnamecallback : this.classnamecallback 1083 }; 1084 this.fpnode.one('.fp-content').fp_display_filelist(options, list, this.lazyloading); 1085 }, 1086 /** displays list of files in table view mode. If param appenditems is specified, 1087 * appends those items to the end of the list. Otherwise (default behaviour) 1088 * clears the contents and displays the items from this.filelist */ 1089 view_as_table: function(appenditems) { 1090 this.viewmode = 3; 1091 var list = (appenditems != null) ? appenditems : this.filelist; 1092 if (!appenditems && (!this.filelist || this.filelist.length==0) && !this.active_repo.hasmorepages) { 1093 this.display_error(M.util.get_string('nofilesavailable', 'repository'), 'nofilesavailable'); 1094 return; 1095 } 1096 var element_template = Y.Node.create(M.core_filepicker.templates.listfilename); 1097 var options = { 1098 viewmode : this.viewmode, 1099 appendonly : (appenditems != null), 1100 filenode : element_template, 1101 callbackcontext : this, 1102 sortable : !this.active_repo.hasmorepages, 1103 callback : function(e, node) { 1104 if (e.preventDefault) {e.preventDefault();} 1105 if (node.children) { 1106 if (this.active_repo.dynload) { 1107 this.list({'path':node.path}); 1108 } else { 1109 this.filelist = node.children; 1110 this.view_files(); 1111 } 1112 } else { 1113 this.select_file(node); 1114 } 1115 }, 1116 classnamecallback : this.classnamecallback 1117 }; 1118 this.fpnode.one('.fp-content').fp_display_filelist(options, list, this.lazyloading); 1119 }, 1120 /** If more than one page available, requests and displays the files from the next page */ 1121 request_next_page: function() { 1122 if (!this.active_repo.hasmorepages || this.active_repo.nextpagerequested) { 1123 // nothing to load 1124 return; 1125 } 1126 this.active_repo.nextpagerequested = true; 1127 var nextpage = this.active_repo.page+1; 1128 var args = { 1129 page: nextpage, 1130 repo_id: this.active_repo.id 1131 }; 1132 var action = this.active_repo.issearchresult ? 'search' : 'list'; 1133 this.request({ 1134 path: this.currentpath, 1135 scope: this, 1136 action: action, 1137 client_id: this.options.client_id, 1138 repository_id: args.repo_id, 1139 params: args, 1140 callback: function(id, obj, args) { 1141 var scope = args.scope; 1142 // Check that we are still in the same repository and are expecting this page. We have no way 1143 // to compare the requested page and the one returned, so we assume that if the last chunk 1144 // of the breadcrumb is similar, then we probably are on the same page. 1145 var samepage = true; 1146 if (obj.path && scope.filepath) { 1147 var pathbefore = scope.filepath[scope.filepath.length-1]; 1148 var pathafter = obj.path[obj.path.length-1]; 1149 if (pathbefore.path != pathafter.path) { 1150 samepage = false; 1151 } 1152 } 1153 if (scope.active_repo.hasmorepages && obj.list && obj.page && 1154 obj.repo_id == scope.active_repo.id && 1155 obj.page == scope.active_repo.page+1 && samepage) { 1156 scope.parse_repository_options(obj, true); 1157 scope.view_files(obj.list) 1158 } 1159 } 1160 }, false); 1161 }, 1162 select_file: function(args) { 1163 var argstitle = args.shorttitle ? args.shorttitle : args.title; 1164 // Limit the string length so it fits nicely on mobile devices 1165 var titlelength = 30; 1166 if (argstitle.length > titlelength) { 1167 argstitle = argstitle.substring(0, titlelength) + '...'; 1168 } 1169 Y.one('#fp-file_label_'+this.options.client_id).setContent(Y.Escape.html(M.util.get_string('select', 'repository')+' '+argstitle)); 1170 this.selectui.show(); 1171 Y.one('#'+this.selectnode.get('id')).focus(); 1172 var client_id = this.options.client_id; 1173 var selectnode = this.selectnode; 1174 var return_types = this.options.repositories[this.active_repo.id].return_types; 1175 selectnode.removeClass('loading'); 1176 selectnode.one('.fp-saveas input').set('value', args.title); 1177 1178 var imgnode = Y.Node.create('<img/>'). 1179 set('src', args.realthumbnail ? args.realthumbnail : args.thumbnail). 1180 setStyle('maxHeight', ''+(args.thumbnail_height ? args.thumbnail_height : 90)+'px'). 1181 setStyle('maxWidth', ''+(args.thumbnail_width ? args.thumbnail_width : 90)+'px'); 1182 selectnode.one('.fp-thumbnail').setContent('').appendChild(imgnode); 1183 1184 // filelink is the array of file-link-types available for this repository in this env 1185 var filelinktypes = [2/*FILE_INTERNAL*/,1/*FILE_EXTERNAL*/,4/*FILE_REFERENCE*/,8/*FILE_CONTROLLED_LINK*/]; 1186 var filelink = {}, firstfilelink = null, filelinkcount = 0; 1187 for (var i in filelinktypes) { 1188 var allowed = (return_types & filelinktypes[i]) && 1189 (this.options.return_types & filelinktypes[i]); 1190 if (filelinktypes[i] == 1/*FILE_EXTERNAL*/ && !this.options.externallink && this.options.env == 'editor') { 1191 // special configuration setting 'repositoryallowexternallinks' may prevent 1192 // using external links in editor environment 1193 allowed = false; 1194 } 1195 filelink[filelinktypes[i]] = allowed; 1196 firstfilelink = (firstfilelink==null && allowed) ? filelinktypes[i] : firstfilelink; 1197 filelinkcount += allowed ? 1 : 0; 1198 } 1199 var defaultreturntype = this.options.repositories[this.active_repo.id].defaultreturntype; 1200 if (defaultreturntype) { 1201 if (filelink[defaultreturntype]) { 1202 firstfilelink = defaultreturntype; 1203 } 1204 } 1205 // make radio buttons enabled if this file-link-type is available and only if there are more than one file-link-type option 1206 // check the first available file-link-type option 1207 for (var linktype in filelink) { 1208 var el = selectnode.one('.fp-linktype-'+linktype); 1209 el.addClassIf('uneditable', !(filelink[linktype] && filelinkcount>1)); 1210 el.one('input').set('checked', (firstfilelink == linktype) ? 'checked' : '').simulate('change'); 1211 } 1212 1213 // TODO MDL-32532: attributes 'hasauthor' and 'haslicense' need to be obsolete, 1214 selectnode.one('.fp-setauthor input').set('value', args.author ? args.author : this.options.author); 1215 this.populateLicensesSelect(selectnode.one('.fp-setlicense select'), args); 1216 selectnode.one('form #filesource-'+client_id).set('value', args.source); 1217 selectnode.one('form #filesourcekey-'+client_id).set('value', args.sourcekey); 1218 1219 // display static information about a file (when known) 1220 var attrs = ['datemodified','datecreated','size','license','author','dimensions']; 1221 for (var i in attrs) { 1222 if (selectnode.one('.fp-'+attrs[i])) { 1223 var value = (args[attrs[i]+'_f']) ? args[attrs[i]+'_f'] : (args[attrs[i]] ? args[attrs[i]] : ''); 1224 selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '') 1225 .one('.fp-value').setContent(Y.Escape.html(value)); 1226 } 1227 } 1228 }, 1229 setup_select_file: function() { 1230 var client_id = this.options.client_id; 1231 var selectnode = this.selectnode; 1232 var getfile = selectnode.one('.fp-select-confirm'); 1233 var filePickerHelper = this; 1234 // bind labels with corresponding inputs 1235 selectnode.all('.fp-saveas,.fp-linktype-2,.fp-linktype-1,.fp-linktype-4,fp-linktype-8,.fp-setauthor,.fp-setlicense').each(function (node) { 1236 node.all('label').set('for', node.one('input,select').generateID()); 1237 }); 1238 selectnode.one('.fp-linktype-2 input').setAttrs({value: 2, name: 'linktype'}); 1239 selectnode.one('.fp-linktype-1 input').setAttrs({value: 1, name: 'linktype'}); 1240 selectnode.one('.fp-linktype-4 input').setAttrs({value: 4, name: 'linktype'}); 1241 selectnode.one('.fp-linktype-8 input').setAttrs({value: 8, name: 'linktype'}); 1242 var changelinktype = function(e) { 1243 if (e.currentTarget.get('checked')) { 1244 var allowinputs = e.currentTarget.get('value') != 1/*FILE_EXTERNAL*/; 1245 selectnode.all('.fp-setauthor,.fp-setlicense,.fp-saveas').each(function(node){ 1246 node.addClassIf('uneditable', !allowinputs); 1247 node.all('input,select').set('disabled', allowinputs?'':'disabled'); 1248 }); 1249 1250 // If the link to the file is selected, only then. 1251 // Remember: this is not to be done for all repos. 1252 // Only for those repos where the filereferencewarning is set. 1253 // The value 4 represents FILE_REFERENCE here. 1254 if (e.currentTarget.get('value') === '4') { 1255 var filereferencewarning = filePickerHelper.active_repo.filereferencewarning; 1256 if (filereferencewarning) { 1257 var fileReferenceNode = e.currentTarget.ancestor('.fp-linktype-4'); 1258 var fileReferenceWarningNode = Y.Node.create('<div/>'). 1259 addClass('alert alert-warning px-3 py-1 my-1 small'). 1260 setAttrs({role: 'alert'}). 1261 setContent(filereferencewarning); 1262 fileReferenceNode.append(fileReferenceWarningNode); 1263 } 1264 } else { 1265 var fileReferenceInput = selectnode.one('.fp-linktype-4 input'); 1266 var fileReferenceWarningNode = fileReferenceInput.ancestor('.fp-linktype-4').one('.alert-warning'); 1267 if (fileReferenceWarningNode) { 1268 fileReferenceWarningNode.remove(); 1269 } 1270 } 1271 } 1272 }; 1273 selectnode.all('.fp-linktype-2,.fp-linktype-1,.fp-linktype-4,.fp-linktype-8').each(function (node) { 1274 node.one('input').on('change', changelinktype, this); 1275 }); 1276 // register event on clicking submit button 1277 getfile.on('click', function(e) { 1278 e.preventDefault(); 1279 var client_id = this.options.client_id; 1280 var scope = this; 1281 var repository_id = this.active_repo.id; 1282 var title = selectnode.one('.fp-saveas input').get('value'); 1283 var filesource = selectnode.one('form #filesource-'+client_id).get('value'); 1284 var filesourcekey = selectnode.one('form #filesourcekey-'+client_id).get('value'); 1285 var params = {'title':title, 'source':filesource, 'savepath': this.options.savepath, sourcekey: filesourcekey}; 1286 var license = selectnode.one('.fp-setlicense select'); 1287 if (license) { 1288 params['license'] = license.get('value'); 1289 var origlicense = selectnode.one('.fp-license .fp-value'); 1290 if (origlicense) { 1291 origlicense = origlicense.getContent(); 1292 } 1293 if (this.options.rememberuserlicensepref) { 1294 this.set_preference('recentlicense', license.get('value')); 1295 } 1296 } 1297 params['author'] = selectnode.one('.fp-setauthor input').get('value'); 1298 1299 var return_types = this.options.repositories[this.active_repo.id].return_types; 1300 if (this.options.env == 'editor') { 1301 // in editor, images are stored in '/' only 1302 params.savepath = '/'; 1303 } 1304 if ((this.options.externallink || this.options.env != 'editor') && 1305 (return_types & 1/*FILE_EXTERNAL*/) && 1306 (this.options.return_types & 1/*FILE_EXTERNAL*/) && 1307 selectnode.one('.fp-linktype-1 input').get('checked')) { 1308 params['linkexternal'] = 'yes'; 1309 } else if ((return_types & 4/*FILE_REFERENCE*/) && 1310 (this.options.return_types & 4/*FILE_REFERENCE*/) && 1311 selectnode.one('.fp-linktype-4 input').get('checked')) { 1312 params['usefilereference'] = '1'; 1313 } else if ((return_types & 8/*FILE_CONTROLLED_LINK*/) && 1314 (this.options.return_types & 8/*FILE_CONTROLLED_LINK*/) && 1315 selectnode.one('.fp-linktype-8 input').get('checked')) { 1316 params['usecontrolledlink'] = '1'; 1317 } 1318 1319 selectnode.addClass('loading'); 1320 this.request({ 1321 action:'download', 1322 client_id: client_id, 1323 repository_id: repository_id, 1324 'params': params, 1325 onerror: function(id, obj, args) { 1326 selectnode.removeClass('loading'); 1327 scope.selectui.hide(); 1328 }, 1329 callback: function(id, obj, args) { 1330 selectnode.removeClass('loading'); 1331 if (obj.event == 'fileexists') { 1332 scope.process_existing_file(obj); 1333 return; 1334 } 1335 if (scope.options.editor_target && scope.options.env=='editor') { 1336 scope.options.editor_target.value=obj.url; 1337 scope.options.editor_target.dispatchEvent(new Event('change'), {'bubbles': true}); 1338 } 1339 scope.hide(); 1340 obj.client_id = client_id; 1341 var formcallback_scope = args.scope.options.magicscope ? args.scope.options.magicscope : args.scope; 1342 scope.options.formcallback.apply(formcallback_scope, [obj]); 1343 } 1344 }, false); 1345 }, this); 1346 var elform = selectnode.one('form'); 1347 elform.appendChild(Y.Node.create('<input/>'). 1348 setAttrs({type:'hidden',id:'filesource-'+client_id})); 1349 elform.appendChild(Y.Node.create('<input/>'). 1350 setAttrs({type:'hidden',id:'filesourcekey-'+client_id})); 1351 elform.on('keydown', function(e) { 1352 if (e.keyCode == 13) { 1353 getfile.simulate('click'); 1354 e.preventDefault(); 1355 } 1356 }, this); 1357 var cancel = selectnode.one('.fp-select-cancel'); 1358 cancel.on('click', function(e) { 1359 e.preventDefault(); 1360 this.selectui.hide(); 1361 }, this); 1362 }, 1363 wait: function() { 1364 // First check there isn't already an interval in play, and if there is kill it now. 1365 if (this.waitinterval != null) { 1366 clearInterval(this.waitinterval); 1367 } 1368 // Prepare the root node we will set content for and the loading template we want to display as a YUI node. 1369 var root = this.fpnode.one('.fp-content'); 1370 var content = Y.Node.create(M.core_filepicker.templates.loading).addClass('fp-content-hidden').setStyle('opacity', 0); 1371 var count = 0; 1372 // Initiate an interval, we will have a count which will increment every 100 milliseconds. 1373 // Count 0 - the loading icon will have visibility set to hidden (invisible) and have an opacity of 0 (invisible also) 1374 // Count 5 - the visiblity will be switched to visible but opacity will still be at 0 (inivisible) 1375 // Counts 6 - 15 opacity will be increased by 0.1 making the loading icon visible over the period of a second 1376 // Count 16 - The interval will be cancelled. 1377 var interval = setInterval(function(){ 1378 if (!content || !root.contains(content) || count >= 15) { 1379 clearInterval(interval); 1380 return true; 1381 } 1382 if (count == 5) { 1383 content.removeClass('fp-content-hidden'); 1384 } else if (count > 5) { 1385 var opacity = parseFloat(content.getStyle('opacity')); 1386 content.setStyle('opacity', opacity + 0.1); 1387 } 1388 count++; 1389 return false; 1390 }, 100); 1391 // Store the wait interval so that we can check it in the future. 1392 this.waitinterval = interval; 1393 // Set the content to the loading template. 1394 root.setContent(content); 1395 }, 1396 viewbar_set_enabled: function(mode) { 1397 var viewbar = this.fpnode.one('.fp-viewbar') 1398 if (viewbar) { 1399 if (mode) { 1400 viewbar.addClass('enabled').removeClass('disabled'); 1401 this.fpnode.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').setAttribute("aria-disabled", "false"); 1402 this.fpnode.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').setAttribute("tabindex", ""); 1403 } else { 1404 viewbar.removeClass('enabled').addClass('disabled'); 1405 this.fpnode.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').setAttribute("aria-disabled", "true"); 1406 this.fpnode.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').setAttribute("tabindex", "-1"); 1407 } 1408 } 1409 this.fpnode.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked'); 1410 var modes = {1:'icons', 2:'tree', 3:'details'}; 1411 this.fpnode.all('.fp-vb-'+modes[this.viewmode]).addClass('checked'); 1412 }, 1413 viewbar_clicked: function(e) { 1414 e.preventDefault(); 1415 var viewbar = this.fpnode.one('.fp-viewbar') 1416 if (!viewbar || !viewbar.hasClass('disabled')) { 1417 if (e.currentTarget.hasClass('fp-vb-tree')) { 1418 this.viewmode = 2; 1419 } else if (e.currentTarget.hasClass('fp-vb-details')) { 1420 this.viewmode = 3; 1421 } else { 1422 this.viewmode = 1; 1423 } 1424 this.viewbar_set_enabled(true) 1425 this.view_files(); 1426 this.set_preference('recentviewmode', this.viewmode); 1427 } 1428 }, 1429 render: function() { 1430 var client_id = this.options.client_id; 1431 var fpid = "filepicker-"+ client_id; 1432 var labelid = 'fp-dialog-label_'+ client_id; 1433 var width = 873; 1434 var draggable = true; 1435 this.fpnode = Y.Node.create(M.core_filepicker.templates.generallayout). 1436 set('id', 'filepicker-'+client_id).set('aria-labelledby', labelid); 1437 1438 if (this.in_iframe()) { 1439 width = Math.floor(window.innerWidth * 0.95); 1440 draggable = false; 1441 } 1442 1443 this.mainui = new M.core.dialogue({ 1444 extraClasses : ['filepicker'], 1445 draggable : draggable, 1446 bodyContent : this.fpnode, 1447 headerContent: '<h3 id="'+ labelid +'">'+ M.util.get_string('filepicker', 'repository') +'</h3>', 1448 centered : true, 1449 modal : true, 1450 visible : false, 1451 width : width+'px', 1452 responsiveWidth : 768, 1453 height : '558px', 1454 zIndex : this.options.zIndex, 1455 focusOnPreviousTargetAfterHide: true, 1456 focusAfterHide: this.options.previousActiveElement 1457 }); 1458 1459 // create panel for selecting a file (initially hidden) 1460 this.selectnode = Y.Node.create(M.core_filepicker.templates.selectlayout). 1461 set('id', 'filepicker-select-'+client_id). 1462 set('aria-live', 'assertive'). 1463 set('role', 'dialog'); 1464 1465 var fplabel = 'fp-file_label_'+ client_id; 1466 this.selectui = new M.core.dialogue({ 1467 headerContent: '<h3 id="' + fplabel +'">'+M.util.get_string('select', 'repository')+'</h3>', 1468 draggable : true, 1469 width : '450px', 1470 bodyContent : this.selectnode, 1471 centered : true, 1472 modal : true, 1473 visible : false, 1474 zIndex : this.options.zIndex 1475 }); 1476 Y.one('#'+this.selectnode.get('id')).setAttribute('aria-labelledby', fplabel); 1477 // event handler for lazy loading of thumbnails and next page 1478 this.fpnode.one('.fp-content').on(['scroll','resize'], this.content_scrolled, this); 1479 // save template for one path element and location of path bar 1480 if (this.fpnode.one('.fp-path-folder')) { 1481 this.pathnode = this.fpnode.one('.fp-path-folder'); 1482 this.pathbar = this.pathnode.get('parentNode'); 1483 this.pathbar.removeChild(this.pathnode); 1484 } 1485 // assign callbacks for view mode switch buttons 1486 this.fpnode.one('.fp-vb-icons').on('click', this.viewbar_clicked, this); 1487 this.fpnode.one('.fp-vb-tree').on('click', this.viewbar_clicked, this); 1488 this.fpnode.one('.fp-vb-details').on('click', this.viewbar_clicked, this); 1489 1490 // assign callbacks for toolbar links 1491 this.setup_toolbar(); 1492 this.setup_select_file(); 1493 this.hide_header(); 1494 1495 // processing repository listing 1496 // Resort the repositories by sortorder 1497 var sorted_repositories = []; 1498 var i; 1499 for (i in this.options.repositories) { 1500 sorted_repositories[i] = this.options.repositories[i]; 1501 } 1502 sorted_repositories.sort(function(a,b){return a.sortorder-b.sortorder}); 1503 // extract one repository template and repeat it for all repositories available, 1504 // set name and icon and assign callbacks 1505 var reponode = this.fpnode.one('.fp-repo'); 1506 if (reponode) { 1507 var list = reponode.get('parentNode'); 1508 list.removeChild(reponode); 1509 for (i in sorted_repositories) { 1510 var repository = sorted_repositories[i]; 1511 var h = (parseInt(i) == 0) ? parseInt(i) : parseInt(i) - 1, 1512 j = (parseInt(i) == Object.keys(sorted_repositories).length - 1) ? parseInt(i) : parseInt(i) + 1; 1513 var previousrepository = sorted_repositories[h]; 1514 var nextrepository = sorted_repositories[j]; 1515 var node = reponode.cloneNode(true); 1516 list.appendChild(node); 1517 node. 1518 set('id', 'fp-repo-'+client_id+'-'+repository.id). 1519 on('click', function(e, repository_id) { 1520 e.preventDefault(); 1521 this.set_preference('recentrepository', repository_id); 1522 this.hide_header(); 1523 this.list({'repo_id':repository_id}); 1524 }, this /*handler running scope*/, repository.id/*second argument of handler*/); 1525 node.on('key', function(e, previousrepositoryid, nextrepositoryid, clientid, repositoryid) { 1526 this.changeHighlightedRepository(e, clientid, repositoryid, previousrepositoryid, nextrepositoryid); 1527 }, 'down:38,40', this, previousrepository.id, nextrepository.id, client_id, repository.id); 1528 node.on('key', function(e, repositoryid) { 1529 e.preventDefault(); 1530 this.set_preference('recentrepository', repositoryid); 1531 this.hide_header(); 1532 this.list({'repo_id': repositoryid}); 1533 }, 'enter', this, repository.id); 1534 node.one('.fp-repo-name').setContent(Y.Escape.html(repository.name)); 1535 node.one('.fp-repo-icon').set('src', repository.icon); 1536 if (i==0) { 1537 node.addClass('first'); 1538 } 1539 if (i==sorted_repositories.length-1) { 1540 node.addClass('last'); 1541 } 1542 if (i%2) { 1543 node.addClass('even'); 1544 } else { 1545 node.addClass('odd'); 1546 } 1547 } 1548 } 1549 // display error if no repositories found 1550 if (sorted_repositories.length==0) { 1551 this.display_error(M.util.get_string('norepositoriesavailable', 'repository'), 'norepositoriesavailable') 1552 } 1553 // display repository that was used last time 1554 this.mainui.show(); 1555 this.show_recent_repository(); 1556 }, 1557 /** 1558 * Change the highlighted repository to a new one. 1559 * 1560 * @param {object} event The key event 1561 * @param {integer} clientid The client id to identify the repo class. 1562 * @param {integer} oldrepositoryid The repository id that we are removing the highlight for 1563 * @param {integer} previousrepositoryid The previous repository id. 1564 * @param {integer} nextrepositoryid The next repository id. 1565 */ 1566 changeHighlightedRepository: function(event, clientid, oldrepositoryid, previousrepositoryid, nextrepositoryid) { 1567 event.preventDefault(); 1568 var newrepositoryid = (event.keyCode == '40') ? nextrepositoryid : previousrepositoryid; 1569 this.fpnode.one('#fp-repo-' + clientid + '-' + oldrepositoryid).setAttribute('tabindex', '-1'); 1570 this.fpnode.one('#fp-repo-' + clientid + '-' + newrepositoryid) 1571 .setAttribute('tabindex', '0') 1572 .focus(); 1573 }, 1574 parse_repository_options: function(data, appendtolist) { 1575 if (appendtolist) { 1576 if (data.list) { 1577 if (!this.filelist) { 1578 this.filelist = []; 1579 } 1580 for (var i in data.list) { 1581 this.filelist[this.filelist.length] = data.list[i]; 1582 } 1583 } 1584 } else { 1585 this.filelist = data.list?data.list:null; 1586 this.lazyloading = {}; 1587 } 1588 this.filepath = data.path?data.path:null; 1589 this.objecttag = data.object?data.object:null; 1590 this.active_repo = {}; 1591 this.active_repo.issearchresult = data.issearchresult ? true : false; 1592 this.active_repo.defaultreturntype = data.defaultreturntype?data.defaultreturntype:null; 1593 this.active_repo.dynload = data.dynload?data.dynload:false; 1594 this.active_repo.pages = Number(data.pages?data.pages:null); 1595 this.active_repo.page = Number(data.page?data.page:null); 1596 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)) 1597 this.active_repo.id = data.repo_id?data.repo_id:null; 1598 this.active_repo.nosearch = (data.login || data.nosearch); // this is either login form or 'nosearch' attribute set 1599 this.active_repo.norefresh = (data.login || data.norefresh); // this is either login form or 'norefresh' attribute set 1600 this.active_repo.nologin = (data.login || data.nologin); // this is either login form or 'nologin' attribute is set 1601 this.active_repo.logouttext = data.logouttext?data.logouttext:null; 1602 this.active_repo.logouturl = (data.logouturl || ''); 1603 this.active_repo.message = (data.message || ''); 1604 this.active_repo.help = data.help?data.help:null; 1605 this.active_repo.manage = data.manage?data.manage:null; 1606 // Warning message related to the file reference option, if applicable to the given repository. 1607 this.active_repo.filereferencewarning = data.filereferencewarning ? data.filereferencewarning : null; 1608 this.print_header(); 1609 }, 1610 print_login: function(data) { 1611 this.parse_repository_options(data); 1612 var client_id = this.options.client_id; 1613 var repository_id = data.repo_id; 1614 var l = this.logindata = data.login; 1615 var loginurl = ''; 1616 var action = data['login_btn_action'] ? data['login_btn_action'] : 'login'; 1617 var form_id = 'fp-form-'+client_id; 1618 1619 var loginform_node = Y.Node.create(M.core_filepicker.templates.loginform); 1620 loginform_node.one('form').set('id', form_id); 1621 this.fpnode.one('.fp-content').setContent('').appendChild(loginform_node); 1622 var templates = { 1623 'popup' : loginform_node.one('.fp-login-popup'), 1624 'textarea' : loginform_node.one('.fp-login-textarea'), 1625 'select' : loginform_node.one('.fp-login-select'), 1626 'text' : loginform_node.one('.fp-login-text'), 1627 'radio' : loginform_node.one('.fp-login-radiogroup'), 1628 'checkbox' : loginform_node.one('.fp-login-checkbox'), 1629 'input' : loginform_node.one('.fp-login-input') 1630 }; 1631 var container; 1632 for (var i in templates) { 1633 if (templates[i]) { 1634 container = templates[i].get('parentNode'); 1635 container.removeChild(templates[i]); 1636 } 1637 } 1638 1639 for(var k in l) { 1640 if (templates[l[k].type]) { 1641 var node = templates[l[k].type].cloneNode(true); 1642 } else { 1643 node = templates['input'].cloneNode(true); 1644 } 1645 if (l[k].type == 'popup') { 1646 // submit button 1647 loginurl = l[k].url; 1648 var popupbutton = node.one('button'); 1649 popupbutton.on('click', function(e){ 1650 M.core_filepicker.active_filepicker = this; 1651 window.open(loginurl, 'repo_auth', 'location=0,status=0,width=500,height=300,scrollbars=yes'); 1652 e.preventDefault(); 1653 }, this); 1654 loginform_node.one('form').on('keydown', function(e) { 1655 if (e.keyCode == 13) { 1656 popupbutton.simulate('click'); 1657 e.preventDefault(); 1658 } 1659 }, this); 1660 loginform_node.all('.fp-login-submit').remove(); 1661 action = 'popup'; 1662 } else if(l[k].type=='textarea') { 1663 // textarea element 1664 if (node.one('label')) { 1665 node.one('label').set('for', l[k].id).setContent(l[k].label); 1666 } 1667 node.one('textarea').setAttrs({id:l[k].id, name:l[k].name}); 1668 } else if(l[k].type=='select') { 1669 // select element 1670 if (node.one('label')) { 1671 node.one('label').set('for', l[k].id).setContent(l[k].label); 1672 } 1673 node.one('select').setAttrs({id:l[k].id, name:l[k].name}).setContent(''); 1674 for (i in l[k].options) { 1675 node.one('select').appendChild( 1676 Y.Node.create('<option/>'). 1677 set('value', l[k].options[i].value). 1678 setContent(l[k].options[i].label)); 1679 } 1680 } else if(l[k].type=='radio') { 1681 // radio input element 1682 node.all('label').setContent(l[k].label); 1683 var list = l[k].value.split('|'); 1684 var labels = l[k].value_label.split('|'); 1685 var radionode = null; 1686 for(var item in list) { 1687 if (radionode == null) { 1688 radionode = node.one('.fp-login-radio'); 1689 radionode.one('input').set('checked', 'checked'); 1690 } else { 1691 var x = radionode.cloneNode(true); 1692 radionode.insert(x, 'after'); 1693 radionode = x; 1694 radionode.one('input').set('checked', ''); 1695 } 1696 radionode.one('input').setAttrs({id:''+l[k].id+item, name:l[k].name, 1697 type:l[k].type, value:list[item]}); 1698 radionode.all('label').setContent(labels[item]).set('for', ''+l[k].id+item) 1699 } 1700 if (radionode == null) { 1701 node.one('.fp-login-radio').remove(); 1702 } 1703 } else { 1704 // input element 1705 if (node.one('label')) { node.one('label').set('for', l[k].id).setContent(l[k].label) } 1706 node.one('input'). 1707 set('type', l[k].type). 1708 set('id', l[k].id). 1709 set('name', l[k].name). 1710 set('value', l[k].value?l[k].value:'') 1711 } 1712 container.appendChild(node); 1713 } 1714 // custom label text for submit button 1715 if (data['login_btn_label']) { 1716 loginform_node.all('.fp-login-submit').setContent(data['login_btn_label']) 1717 } 1718 // register button action for login and search 1719 if (action == 'login' || action == 'search') { 1720 loginform_node.one('.fp-login-submit').on('click', function(e){ 1721 e.preventDefault(); 1722 this.hide_header(); 1723 this.request({ 1724 'scope': this, 1725 'action':(action == 'search') ? 'search' : 'signin', 1726 'path': '', 1727 'client_id': client_id, 1728 'repository_id': repository_id, 1729 'form': {id:form_id, upload:false, useDisabled:true}, 1730 'callback': this.display_response 1731 }, true); 1732 }, this); 1733 } 1734 // if 'Enter' is pressed in the form, simulate the button click 1735 if (loginform_node.one('.fp-login-submit')) { 1736 loginform_node.one('form').on('keydown', function(e) { 1737 if (e.keyCode == 13) { 1738 loginform_node.one('.fp-login-submit').simulate('click') 1739 e.preventDefault(); 1740 } 1741 }, this); 1742 } 1743 }, 1744 display_response: function(id, obj, args) { 1745 var scope = args.scope; 1746 // highlight the current repository in repositories list 1747 scope.fpnode.all('.fp-repo.active') 1748 .removeClass('active') 1749 .setAttribute('aria-selected', 'false') 1750 .setAttribute('tabindex', '-1'); 1751 scope.fpnode.all('.nav-link') 1752 .removeClass('active') 1753 .setAttribute('aria-selected', 'false') 1754 .setAttribute('tabindex', '-1'); 1755 var activenode = scope.fpnode.one('#fp-repo-' + scope.options.client_id + '-' + obj.repo_id); 1756 activenode.addClass('active') 1757 .setAttribute('aria-selected', 'true') 1758 .setAttribute('tabindex', '0'); 1759 activenode.all('.nav-link').addClass('active'); 1760 // add class repository_REPTYPE to the filepicker (for repository-specific styles) 1761 for (var i in scope.options.repositories) { 1762 scope.fpnode.removeClass('repository_'+scope.options.repositories[i].type) 1763 } 1764 if (obj.repo_id && scope.options.repositories[obj.repo_id]) { 1765 scope.fpnode.addClass('repository_'+scope.options.repositories[obj.repo_id].type) 1766 } 1767 Y.one('.file-picker .fp-repo-items').focus(); 1768 1769 // display response 1770 if (obj.login) { 1771 scope.viewbar_set_enabled(false); 1772 scope.print_login(obj); 1773 } else if (obj.upload) { 1774 scope.viewbar_set_enabled(false); 1775 scope.parse_repository_options(obj); 1776 scope.create_upload_form(obj); 1777 } else if (obj.object) { 1778 M.core_filepicker.active_filepicker = scope; 1779 scope.viewbar_set_enabled(false); 1780 scope.parse_repository_options(obj); 1781 scope.create_object_container(obj.object); 1782 } else if (obj.list) { 1783 scope.viewbar_set_enabled(true); 1784 scope.parse_repository_options(obj); 1785 scope.view_files(); 1786 } 1787 }, 1788 list: function(args) { 1789 if (!args) { 1790 args = {}; 1791 } 1792 if (!args.repo_id) { 1793 args.repo_id = this.active_repo.id; 1794 } 1795 if (!args.path) { 1796 args.path = ''; 1797 } 1798 this.currentpath = args.path; 1799 this.request({ 1800 action: 'list', 1801 client_id: this.options.client_id, 1802 repository_id: args.repo_id, 1803 path: args.path, 1804 page: args.page, 1805 scope: this, 1806 callback: this.display_response 1807 }, true); 1808 }, 1809 populateLicensesSelect: function(licensenode, filenode) { 1810 if (!licensenode) { 1811 return; 1812 } 1813 licensenode.setContent(''); 1814 var selectedlicense = this.options.defaultlicense; 1815 if (filenode) { 1816 // File has a license already, use it. 1817 selectedlicense = filenode.license; 1818 } else if (this.options.rememberuserlicensepref && this.get_preference('recentlicense')) { 1819 // When 'Remember user licence preference' is enabled use the last license selected by the user, if any. 1820 selectedlicense = this.get_preference('recentlicense'); 1821 } 1822 var licenses = this.options.licenses; 1823 for (var i in licenses) { 1824 // Include the file's current license, even if not enabled, to prevent displaying 1825 // misleading information about which license the file currently has assigned to it. 1826 if (licenses[i].enabled == true || (filenode !== undefined && licenses[i].shortname === filenode.license)) { 1827 var option = Y.Node.create('<option/>'). 1828 set('selected', (licenses[i].shortname == selectedlicense)). 1829 set('value', licenses[i].shortname). 1830 setContent(Y.Escape.html(licenses[i].fullname)); 1831 licensenode.appendChild(option); 1832 } 1833 } 1834 }, 1835 create_object_container: function(data) { 1836 var content = this.fpnode.one('.fp-content'); 1837 content.setContent(''); 1838 //var str = '<object data="'+data.src+'" type="'+data.type+'" width="98%" height="98%" id="container_object" class="fp-object-container mdl-align"></object>'; 1839 var container = Y.Node.create('<object/>'). 1840 setAttrs({data:data.src, type:data.type, id:'container_object'}). 1841 addClass('fp-object-container'); 1842 content.setContent('').appendChild(container); 1843 }, 1844 create_upload_form: function(data) { 1845 var client_id = this.options.client_id; 1846 var id = data.upload.id+'_'+client_id; 1847 var content = this.fpnode.one('.fp-content'); 1848 var template_name = 'uploadform_'+this.options.repositories[data.repo_id].type; 1849 var template = M.core_filepicker.templates[template_name] || M.core_filepicker.templates['uploadform']; 1850 content.setContent(template); 1851 1852 content.all('.fp-file,.fp-saveas,.fp-setauthor,.fp-setlicense').each(function (node) { 1853 node.all('label').set('for', node.one('input,select').generateID()); 1854 }); 1855 content.one('form').set('id', id); 1856 content.one('.fp-file input').set('name', 'repo_upload_file'); 1857 if (data.upload.label && content.one('.fp-file label')) { 1858 content.one('.fp-file label').setContent(data.upload.label); 1859 } 1860 content.one('.fp-saveas input').set('name', 'title'); 1861 content.one('.fp-setauthor input').setAttrs({name:'author', value:this.options.author}); 1862 content.one('.fp-setlicense select').set('name', 'license'); 1863 this.populateLicensesSelect(content.one('.fp-setlicense select')); 1864 // append hidden inputs to the upload form 1865 content.one('form').appendChild(Y.Node.create('<input/>'). 1866 setAttrs({type:'hidden',name:'itemid',value:this.options.itemid})); 1867 var types = this.options.accepted_types; 1868 for (var i in types) { 1869 content.one('form').appendChild(Y.Node.create('<input/>'). 1870 setAttrs({type:'hidden',name:'accepted_types[]',value:types[i]})); 1871 } 1872 1873 var scope = this; 1874 content.one('.fp-upload-btn').on('click', function(e) { 1875 e.preventDefault(); 1876 var license = content.one('.fp-setlicense select'); 1877 1878 if (this.options.rememberuserlicensepref) { 1879 this.set_preference('recentlicense', license.get('value')); 1880 } 1881 if (!content.one('.fp-file input').get('value')) { 1882 scope.print_msg(M.util.get_string('nofilesattached', 'repository'), 'error'); 1883 return false; 1884 } 1885 this.hide_header(); 1886 scope.request({ 1887 scope: scope, 1888 action:'upload', 1889 client_id: client_id, 1890 params: {'savepath':scope.options.savepath}, 1891 repository_id: scope.active_repo.id, 1892 form: {id: id, upload:true}, 1893 onerror: function(id, o, args) { 1894 scope.create_upload_form(data); 1895 }, 1896 callback: function(id, o, args) { 1897 if (o.event == 'fileexists') { 1898 scope.create_upload_form(data); 1899 scope.process_existing_file(o); 1900 return; 1901 } 1902 if (scope.options.editor_target&&scope.options.env=='editor') { 1903 scope.options.editor_target.value=o.url; 1904 scope.options.editor_target.dispatchEvent(new Event('change'), {'bubbles': true}); 1905 } 1906 scope.hide(); 1907 o.client_id = client_id; 1908 var formcallback_scope = args.scope.options.magicscope ? args.scope.options.magicscope : args.scope; 1909 scope.options.formcallback.apply(formcallback_scope, [o]); 1910 } 1911 }, true); 1912 }, this); 1913 }, 1914 /** setting handlers and labels for elements in toolbar. Called once during the initial render of filepicker */ 1915 setup_toolbar: function() { 1916 var client_id = this.options.client_id; 1917 var toolbar = this.fpnode.one('.fp-toolbar'); 1918 toolbar.one('.fp-tb-logout').one('a,button').on('click', function(e) { 1919 e.preventDefault(); 1920 if (!this.active_repo.nologin) { 1921 this.hide_header(); 1922 this.request({ 1923 action:'logout', 1924 client_id: this.options.client_id, 1925 repository_id: this.active_repo.id, 1926 path:'', 1927 callback: this.display_response 1928 }, true); 1929 } 1930 if (this.active_repo.logouturl) { 1931 window.open(this.active_repo.logouturl, 'repo_auth', 'location=0,status=0,width=500,height=300,scrollbars=yes'); 1932 } 1933 }, this); 1934 toolbar.one('.fp-tb-refresh').one('a,button').on('click', function(e) { 1935 e.preventDefault(); 1936 if (!this.active_repo.norefresh) { 1937 this.list({ path: this.currentpath }); 1938 } 1939 }, this); 1940 toolbar.one('.fp-tb-search form'). 1941 set('method', 'POST'). 1942 set('id', 'fp-tb-search-'+client_id). 1943 on('submit', function(e) { 1944 e.preventDefault(); 1945 if (!this.active_repo.nosearch) { 1946 this.request({ 1947 scope: this, 1948 action:'search', 1949 client_id: this.options.client_id, 1950 repository_id: this.active_repo.id, 1951 form: {id: 'fp-tb-search-'+client_id, upload:false, useDisabled:true}, 1952 callback: this.display_response 1953 }, true); 1954 } 1955 }, this); 1956 1957 // it does not matter what kind of element is .fp-tb-manage, we create a dummy <a> 1958 // element and use it to open url on click event 1959 var managelnk = Y.Node.create('<a/>'). 1960 setAttrs({id:'fp-tb-manage-'+client_id+'-link', target:'_blank'}). 1961 setStyle('display', 'none'); 1962 toolbar.append(managelnk); 1963 toolbar.one('.fp-tb-manage').one('a,button'). 1964 on('click', function(e) { 1965 e.preventDefault(); 1966 managelnk.simulate('click') 1967 }); 1968 1969 // same with .fp-tb-help 1970 var helplnk = Y.Node.create('<a/>'). 1971 setAttrs({id:'fp-tb-help-'+client_id+'-link', target:'_blank'}). 1972 setStyle('display', 'none'); 1973 toolbar.append(helplnk); 1974 toolbar.one('.fp-tb-help').one('a,button'). 1975 on('click', function(e) { 1976 e.preventDefault(); 1977 helplnk.simulate('click') 1978 }); 1979 }, 1980 hide_header: function() { 1981 if (this.fpnode.one('.fp-toolbar')) { 1982 this.fpnode.one('.fp-toolbar').addClass('empty'); 1983 } 1984 if (this.pathbar) { 1985 this.pathbar.setContent('').addClass('empty'); 1986 } 1987 }, 1988 print_header: function() { 1989 var r = this.active_repo; 1990 var scope = this; 1991 var client_id = this.options.client_id; 1992 this.hide_header(); 1993 this.print_path(); 1994 var toolbar = this.fpnode.one('.fp-toolbar'); 1995 if (!toolbar) { return; } 1996 1997 var enable_tb_control = function(node, enabled) { 1998 if (!node) { return; } 1999 node.addClassIf('disabled', !enabled).addClassIf('enabled', enabled) 2000 if (enabled) { 2001 toolbar.removeClass('empty'); 2002 } 2003 } 2004 2005 // TODO 'back' permanently disabled for now. Note, flickr_public uses 'Logout' for it! 2006 enable_tb_control(toolbar.one('.fp-tb-back'), false); 2007 2008 // search form 2009 enable_tb_control(toolbar.one('.fp-tb-search'), !r.nosearch); 2010 if(!r.nosearch) { 2011 var searchform = toolbar.one('.fp-tb-search form'); 2012 searchform.setContent(''); 2013 this.request({ 2014 scope: this, 2015 action:'searchform', 2016 repository_id: this.active_repo.id, 2017 callback: function(id, obj, args) { 2018 if (obj.repo_id == scope.active_repo.id && obj.form) { 2019 // if we did not jump to another repository meanwhile 2020 searchform.setContent(obj.form); 2021 // Highlight search text when user click for search. 2022 var searchnode = searchform.one('input[name="s"]'); 2023 if (searchnode) { 2024 searchnode.once('click', function(e) { 2025 e.preventDefault(); 2026 this.select(); 2027 }); 2028 } 2029 } 2030 } 2031 }, false); 2032 } 2033 2034 // refresh button 2035 // weather we use cache for this instance, this button will reload listing anyway 2036 enable_tb_control(toolbar.one('.fp-tb-refresh'), !r.norefresh); 2037 2038 // login button 2039 enable_tb_control(toolbar.one('.fp-tb-logout'), !r.nologin); 2040 2041 // manage url 2042 enable_tb_control(toolbar.one('.fp-tb-manage'), r.manage); 2043 Y.one('#fp-tb-manage-'+client_id+'-link').set('href', r.manage); 2044 2045 // help url 2046 enable_tb_control(toolbar.one('.fp-tb-help'), r.help); 2047 Y.one('#fp-tb-help-'+client_id+'-link').set('href', r.help); 2048 2049 // message 2050 enable_tb_control(toolbar.one('.fp-tb-message'), r.message); 2051 toolbar.one('.fp-tb-message').setContent(r.message); 2052 }, 2053 print_path: function() { 2054 if (!this.pathbar) { 2055 return; 2056 } 2057 this.pathbar.setContent('').addClass('empty'); 2058 var p = this.filepath; 2059 if (p && p.length!=0 && this.viewmode != 2) { 2060 for(var i = 0; i < p.length; i++) { 2061 var el = this.pathnode.cloneNode(true); 2062 this.pathbar.appendChild(el); 2063 if (i == 0) { 2064 el.addClass('first'); 2065 } 2066 if (i == p.length-1) { 2067 el.addClass('last'); 2068 } 2069 if (i%2) { 2070 el.addClass('even'); 2071 } else { 2072 el.addClass('odd'); 2073 } 2074 el.all('.fp-path-folder-name').setContent(Y.Escape.html(p[i].name)); 2075 el.on('click', 2076 function(e, path) { 2077 e.preventDefault(); 2078 this.list({'path':path}); 2079 }, 2080 this, p[i].path); 2081 } 2082 this.pathbar.removeClass('empty'); 2083 } 2084 }, 2085 hide: function() { 2086 this.selectui.hide(); 2087 if (this.process_dlg) { 2088 this.process_dlg.hide(); 2089 } 2090 if (this.msg_dlg) { 2091 this.msg_dlg.hide(); 2092 } 2093 this.mainui.hide(); 2094 }, 2095 show: function() { 2096 if (this.fpnode) { 2097 this.hide(); 2098 this.mainui.show(); 2099 this.show_recent_repository(); 2100 } else { 2101 this.launch(); 2102 } 2103 }, 2104 launch: function() { 2105 this.render(); 2106 }, 2107 show_recent_repository: function() { 2108 this.hide_header(); 2109 this.viewbar_set_enabled(false); 2110 var repository_id = this.get_preference('recentrepository'); 2111 this.viewmode = this.get_preference('recentviewmode'); 2112 if (this.viewmode != 2 && this.viewmode != 3) { 2113 this.viewmode = 1; 2114 } 2115 if (this.options.repositories[repository_id]) { 2116 this.list({'repo_id':repository_id}); 2117 } 2118 }, 2119 get_preference: function (name) { 2120 if (this.options.userprefs[name]) { 2121 return this.options.userprefs[name]; 2122 } else { 2123 return false; 2124 } 2125 }, 2126 set_preference: function(name, value) { 2127 if (this.options.userprefs[name] != value) { 2128 M.util.set_user_preference('filepicker_' + name, value); 2129 this.options.userprefs[name] = value; 2130 } 2131 }, 2132 in_iframe: function () { 2133 // If we're not the top window then we're in an iFrame 2134 return window.self !== window.top; 2135 } 2136 }); 2137 var loading = Y.one('#filepicker-loading-'+options.client_id); 2138 if (loading) { 2139 loading.setStyle('display', 'none'); 2140 } 2141 M.core_filepicker.instances[options.client_id] = new FilePickerHelper(options); 2142 };
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 22 11:59:49 2025 | Cross-referenced by PHPXref 0.7.1 |