[ Index ]

PHP Cross Reference of Moodle 310

title

Body

[close]

/repository/ -> filepicker.js (source)

   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  };


Generated: Wed Jan 22 11:59:49 2025 Cross-referenced by PHPXref 0.7.1