[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ -> javascript-static.js (source)

   1  // Miscellaneous core Javascript functions for Moodle
   2  // Global M object is initilised in inline javascript
   3  
   4  /**
   5   * Add module to list of available modules that can be loaded from YUI.
   6   * @param {Array} modules
   7   */
   8  M.yui.add_module = function(modules) {
   9      for (var modname in modules) {
  10          YUI_config.modules[modname] = modules[modname];
  11      }
  12  };
  13  /**
  14   * The gallery version to use when loading YUI modules from the gallery.
  15   * Will be changed every time when using local galleries.
  16   */
  17  M.yui.galleryversion = '2010.04.21-21-51';
  18  
  19  /**
  20   * Various utility functions
  21   */
  22  M.util = M.util || {};
  23  
  24  /**
  25   * Language strings - initialised from page footer.
  26   */
  27  M.str = M.str || {};
  28  
  29  /**
  30   * Returns url for images.
  31   * @param {String} imagename
  32   * @param {String} component
  33   * @return {String}
  34   */
  35  M.util.image_url = function(imagename, component) {
  36  
  37      if (!component || component == '' || component == 'moodle' || component == 'core') {
  38          component = 'core';
  39      }
  40  
  41      var url = M.cfg.wwwroot + '/theme/image.php';
  42      if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
  43          if (!M.cfg.svgicons) {
  44              url += '/_s';
  45          }
  46          url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
  47      } else {
  48          url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
  49          if (!M.cfg.svgicons) {
  50              url += '&svg=0';
  51          }
  52      }
  53  
  54      return url;
  55  };
  56  
  57  M.util.in_array = function(item, array){
  58      for( var i = 0; i<array.length; i++){
  59          if(item==array[i]){
  60              return true;
  61          }
  62      }
  63      return false;
  64  };
  65  
  66  /**
  67   * Init a collapsible region, see print_collapsible_region in weblib.php
  68   * @param {YUI} Y YUI3 instance with all libraries loaded
  69   * @param {String} id the HTML id for the div.
  70   * @param {String} userpref the user preference that records the state of this box. false if none.
  71   * @param {String} strtooltip
  72   */
  73  M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
  74      Y.use('anim', function(Y) {
  75          new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
  76      });
  77  };
  78  
  79  /**
  80   * Object to handle a collapsible region : instantiate and forget styled object
  81   *
  82   * @class
  83   * @constructor
  84   * @param {YUI} Y YUI3 instance with all libraries loaded
  85   * @param {String} id The HTML id for the div.
  86   * @param {String} userpref The user preference that records the state of this box. false if none.
  87   * @param {String} strtooltip
  88   */
  89  M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
  90      // Record the pref name
  91      this.userpref = userpref;
  92  
  93      // Find the divs in the document.
  94      this.div = Y.one('#'+id);
  95  
  96      // Get the caption for the collapsible region
  97      var caption = this.div.one('#'+id + '_caption');
  98  
  99      // Create a link
 100      var a = Y.Node.create('<a href="#"></a>');
 101      a.setAttribute('title', strtooltip);
 102  
 103      // Get all the nodes from caption, remove them and append them to <a>
 104      while (caption.hasChildNodes()) {
 105          child = caption.get('firstChild');
 106          child.remove();
 107          a.append(child);
 108      }
 109      caption.append(a);
 110  
 111      // Get the height of the div at this point before we shrink it if required
 112      var height = this.div.get('offsetHeight');
 113      var collapsedimage = 't/collapsed'; // ltr mode
 114      if (right_to_left()) {
 115          collapsedimage = 't/collapsed_rtl';
 116      } else {
 117          collapsedimage = 't/collapsed';
 118      }
 119      if (this.div.hasClass('collapsed')) {
 120          // Add the correct image and record the YUI node created in the process
 121          this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />');
 122          // Shrink the div as it is collapsed by default
 123          this.div.setStyle('height', caption.get('offsetHeight')+'px');
 124      } else {
 125          // Add the correct image and record the YUI node created in the process
 126          this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
 127      }
 128      a.append(this.icon);
 129  
 130      // Create the animation.
 131      var animation = new Y.Anim({
 132          node: this.div,
 133          duration: 0.3,
 134          easing: Y.Easing.easeBoth,
 135          to: {height:caption.get('offsetHeight')},
 136          from: {height:height}
 137      });
 138  
 139      // Handler for the animation finishing.
 140      animation.on('end', function() {
 141          this.div.toggleClass('collapsed');
 142          var collapsedimage = 't/collapsed'; // ltr mode
 143          if (right_to_left()) {
 144              collapsedimage = 't/collapsed_rtl';
 145              } else {
 146              collapsedimage = 't/collapsed';
 147              }
 148          if (this.div.hasClass('collapsed')) {
 149              this.icon.set('src', M.util.image_url(collapsedimage, 'moodle'));
 150          } else {
 151              this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
 152          }
 153      }, this);
 154  
 155      // Hook up the event handler.
 156      a.on('click', function(e, animation) {
 157          e.preventDefault();
 158          // Animate to the appropriate size.
 159          if (animation.get('running')) {
 160              animation.stop();
 161          }
 162          animation.set('reverse', this.div.hasClass('collapsed'));
 163          // Update the user preference.
 164          if (this.userpref) {
 165              M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
 166          }
 167          animation.run();
 168      }, this, animation);
 169  };
 170  
 171  /**
 172   * The user preference that stores the state of this box.
 173   * @property userpref
 174   * @type String
 175   */
 176  M.util.CollapsibleRegion.prototype.userpref = null;
 177  
 178  /**
 179   * The key divs that make up this
 180   * @property div
 181   * @type Y.Node
 182   */
 183  M.util.CollapsibleRegion.prototype.div = null;
 184  
 185  /**
 186   * The key divs that make up this
 187   * @property icon
 188   * @type Y.Node
 189   */
 190  M.util.CollapsibleRegion.prototype.icon = null;
 191  
 192  /**
 193   * Makes a best effort to connect back to Moodle to update a user preference,
 194   * however, there is no mechanism for finding out if the update succeeded.
 195   *
 196   * Before you can use this function in your JavsScript, you must have called
 197   * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
 198   * the udpate is allowed, and how to safely clean and submitted values.
 199   *
 200   * @param String name the name of the setting to udpate.
 201   * @param String the value to set it to.
 202   */
 203  M.util.set_user_preference = function(name, value) {
 204      YUI().use('io', function(Y) {
 205          var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
 206                  M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
 207  
 208          // If we are a developer, ensure that failures are reported.
 209          var cfg = {
 210                  method: 'get',
 211                  on: {}
 212              };
 213          if (M.cfg.developerdebug) {
 214              cfg.on.failure = function(id, o, args) {
 215                  alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
 216              }
 217          }
 218  
 219          // Make the request.
 220          Y.io(url, cfg);
 221      });
 222  };
 223  
 224  /**
 225   * Prints a confirmation dialog in the style of DOM.confirm().
 226   *
 227   * @method show_confirm_dialog
 228   * @param {EventFacade} e
 229   * @param {Object} args
 230   * @param {String} args.message The question to ask the user
 231   * @param {Function} [args.callback] A callback to apply on confirmation.
 232   * @param {Object} [args.scope] The scope to use when calling the callback.
 233   * @param {Object} [args.callbackargs] Any arguments to pass to the callback.
 234   * @param {String} [args.cancellabel] The label to use on the cancel button.
 235   * @param {String} [args.continuelabel] The label to use on the continue button.
 236   */
 237  M.util.show_confirm_dialog = function(e, args) {
 238      var target = e.target;
 239      if (e.preventDefault) {
 240          e.preventDefault();
 241      }
 242  
 243      YUI().use('moodle-core-notification-confirm', function(Y) {
 244          var confirmationDialogue = new M.core.confirm({
 245              width: '300px',
 246              center: true,
 247              modal: true,
 248              visible: false,
 249              draggable: false,
 250              title: M.util.get_string('confirmation', 'admin'),
 251              noLabel: M.util.get_string('cancel', 'moodle'),
 252              question: args.message
 253          });
 254  
 255          // The dialogue was submitted with a positive value indication.
 256          confirmationDialogue.on('complete-yes', function(e) {
 257              // Handle any callbacks.
 258              if (args.callback) {
 259                  if (!Y.Lang.isFunction(args.callback)) {
 260                      Y.log('Callbacks to show_confirm_dialog must now be functions. Please update your code to pass in a function instead.',
 261                              'warn', 'M.util.show_confirm_dialog');
 262                      return;
 263                  }
 264  
 265                  var scope = e.target;
 266                  if (Y.Lang.isObject(args.scope)) {
 267                      scope = args.scope;
 268                  }
 269  
 270                  var callbackargs = args.callbackargs || [];
 271                  args.callback.apply(scope, callbackargs);
 272                  return;
 273              }
 274  
 275              var targetancestor = null,
 276                  targetform = null;
 277  
 278              if (target.test('a')) {
 279                  window.location = target.get('href');
 280  
 281              } else if ((targetancestor = target.ancestor('a')) !== null) {
 282                  window.location = targetancestor.get('href');
 283  
 284              } else if (target.test('input')) {
 285                  targetform = target.ancestor('form', true);
 286                  if (!targetform) {
 287                      return;
 288                  }
 289                  if (target.get('name') && target.get('value')) {
 290                      targetform.append('<input type="hidden" name="' + target.get('name') +
 291                                      '" value="' + target.get('value') + '">');
 292                  }
 293                  targetform.submit();
 294  
 295              } else if (target.test('form')) {
 296                  target.submit();
 297  
 298              } else {
 299                  Y.log("Element of type " + target.get('tagName') +
 300                          " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM",
 301                          'warn', 'javascript-static');
 302              }
 303          }, this);
 304  
 305          if (args.cancellabel) {
 306              confirmationDialogue.set('noLabel', args.cancellabel);
 307          }
 308  
 309          if (args.continuelabel) {
 310              confirmationDialogue.set('yesLabel', args.continuelabel);
 311          }
 312  
 313          confirmationDialogue.render()
 314                  .show();
 315      });
 316  };
 317  
 318  /** Useful for full embedding of various stuff */
 319  M.util.init_maximised_embed = function(Y, id) {
 320      var obj = Y.one('#'+id);
 321      if (!obj) {
 322          return;
 323      }
 324  
 325      var get_htmlelement_size = function(el, prop) {
 326          if (Y.Lang.isString(el)) {
 327              el = Y.one('#' + el);
 328          }
 329          // Ensure element exists.
 330          if (el) {
 331              var val = el.getStyle(prop);
 332              if (val == 'auto') {
 333                  val = el.getComputedStyle(prop);
 334              }
 335              val = parseInt(val);
 336              if (isNaN(val)) {
 337                  return 0;
 338              }
 339              return val;
 340          } else {
 341              return 0;
 342          }
 343      };
 344  
 345      var resize_object = function() {
 346          obj.setStyle('width', '0px');
 347          obj.setStyle('height', '0px');
 348          var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
 349  
 350          if (newwidth > 500) {
 351              obj.setStyle('width', newwidth  + 'px');
 352          } else {
 353              obj.setStyle('width', '500px');
 354          }
 355  
 356          var headerheight = get_htmlelement_size('page-header', 'height');
 357          var footerheight = get_htmlelement_size('page-footer', 'height');
 358          var newheight = parseInt(Y.one('body').get('winHeight')) - footerheight - headerheight - 100;
 359          if (newheight < 400) {
 360              newheight = 400;
 361          }
 362          obj.setStyle('height', newheight+'px');
 363      };
 364  
 365      resize_object();
 366      // fix layout if window resized too
 367      window.onresize = function() {
 368          resize_object();
 369      };
 370  };
 371  
 372  /**
 373   * Breaks out all links to the top frame - used in frametop page layout.
 374   */
 375  M.util.init_frametop = function(Y) {
 376      Y.all('a').each(function(node) {
 377          node.set('target', '_top');
 378      });
 379      Y.all('form').each(function(node) {
 380          node.set('target', '_top');
 381      });
 382  };
 383  
 384  /**
 385   * Finds all nodes that match the given CSS selector and attaches events to them
 386   * so that they toggle a given classname when clicked.
 387   *
 388   * @param {YUI} Y
 389   * @param {string} id An id containing elements to target
 390   * @param {string} cssselector A selector to use to find targets
 391   * @param {string} toggleclassname A classname to toggle
 392   */
 393  M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
 394  
 395      if (togglecssselector == '') {
 396          togglecssselector = cssselector;
 397      }
 398  
 399      var node = Y.one('#'+id);
 400      node.all(cssselector).each(function(n){
 401          n.on('click', function(e){
 402              e.stopPropagation();
 403              if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
 404                  if (this.test(togglecssselector)) {
 405                      this.toggleClass(toggleclassname);
 406                  } else {
 407                      this.ancestor(togglecssselector).toggleClass(toggleclassname);
 408              }
 409              }
 410          }, n);
 411      });
 412      // Attach this click event to the node rather than all selectors... will be much better
 413      // for performance
 414      node.on('click', function(e){
 415          if (e.target.hasClass('addtoall')) {
 416              this.all(togglecssselector).addClass(toggleclassname);
 417          } else if (e.target.hasClass('removefromall')) {
 418              this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
 419          }
 420      }, node);
 421  };
 422  
 423  /**
 424   * Initialises a colour picker
 425   *
 426   * Designed to be used with admin_setting_configcolourpicker although could be used
 427   * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
 428   * above or below the input (must have the same parent) and then call this with the
 429   * id.
 430   *
 431   * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
 432   * contrib/blocks. For better docs refer to that.
 433   *
 434   * @param {YUI} Y
 435   * @param {int} id
 436   * @param {object} previewconf
 437   */
 438  M.util.init_colour_picker = function(Y, id, previewconf) {
 439      /**
 440       * We need node and event-mouseenter
 441       */
 442      Y.use('node', 'event-mouseenter', function(){
 443          /**
 444           * The colour picker object
 445           */
 446          var colourpicker = {
 447              box : null,
 448              input : null,
 449              image : null,
 450              preview : null,
 451              current : null,
 452              eventClick : null,
 453              eventMouseEnter : null,
 454              eventMouseLeave : null,
 455              eventMouseMove : null,
 456              width : 300,
 457              height :  100,
 458              factor : 5,
 459              /**
 460               * Initalises the colour picker by putting everything together and wiring the events
 461               */
 462              init : function() {
 463                  this.input = Y.one('#'+id);
 464                  this.box = this.input.ancestor().one('.admin_colourpicker');
 465                  this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
 466                  this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
 467                  this.preview = Y.Node.create('<div class="previewcolour"></div>');
 468                  this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
 469                  this.current = Y.Node.create('<div class="currentcolour"></div>');
 470                  this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
 471                  this.box.setContent('').append(this.image).append(this.preview).append(this.current);
 472  
 473                  if (typeof(previewconf) === 'object' && previewconf !== null) {
 474                      Y.one('#'+id+'_preview').on('click', function(e){
 475                          if (Y.Lang.isString(previewconf.selector)) {
 476                              Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
 477                          } else {
 478                              for (var i in previewconf.selector) {
 479                                  Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
 480                              }
 481                          }
 482                      }, this);
 483                  }
 484  
 485                  this.eventClick = this.image.on('click', this.pickColour, this);
 486                  this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
 487              },
 488              /**
 489               * Starts to follow the mouse once it enter the image
 490               */
 491              startFollow : function(e) {
 492                  this.eventMouseEnter.detach();
 493                  this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
 494                  this.eventMouseMove = this.image.on('mousemove', function(e){
 495                      this.preview.setStyle('backgroundColor', this.determineColour(e));
 496                  }, this);
 497              },
 498              /**
 499               * Stops following the mouse
 500               */
 501              endFollow : function(e) {
 502                  this.eventMouseMove.detach();
 503                  this.eventMouseLeave.detach();
 504                  this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
 505              },
 506              /**
 507               * Picks the colour the was clicked on
 508               */
 509              pickColour : function(e) {
 510                  var colour = this.determineColour(e);
 511                  this.input.set('value', colour);
 512                  this.current.setStyle('backgroundColor', colour);
 513              },
 514              /**
 515               * Calculates the colour fromthe given co-ordinates
 516               */
 517              determineColour : function(e) {
 518                  var eventx = Math.floor(e.pageX-e.target.getX());
 519                  var eventy = Math.floor(e.pageY-e.target.getY());
 520  
 521                  var imagewidth = this.width;
 522                  var imageheight = this.height;
 523                  var factor = this.factor;
 524                  var colour = [255,0,0];
 525  
 526                  var matrices = [
 527                      [  0,  1,  0],
 528                      [ -1,  0,  0],
 529                      [  0,  0,  1],
 530                      [  0, -1,  0],
 531                      [  1,  0,  0],
 532                      [  0,  0, -1]
 533                  ];
 534  
 535                  var matrixcount = matrices.length;
 536                  var limit = Math.round(imagewidth/matrixcount);
 537                  var heightbreak = Math.round(imageheight/2);
 538  
 539                  for (var x = 0; x < imagewidth; x++) {
 540                      var divisor = Math.floor(x / limit);
 541                      var matrix = matrices[divisor];
 542  
 543                      colour[0] += matrix[0]*factor;
 544                      colour[1] += matrix[1]*factor;
 545                      colour[2] += matrix[2]*factor;
 546  
 547                      if (eventx==x) {
 548                          break;
 549                      }
 550                  }
 551  
 552                  var pixel = [colour[0], colour[1], colour[2]];
 553                  if (eventy < heightbreak) {
 554                      pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
 555                      pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
 556                      pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
 557                  } else if (eventy > heightbreak) {
 558                      pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
 559                      pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
 560                      pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
 561                  }
 562  
 563                  return this.convert_rgb_to_hex(pixel);
 564              },
 565              /**
 566               * Converts an RGB value to Hex
 567               */
 568              convert_rgb_to_hex : function(rgb) {
 569                  var hex = '#';
 570                  var hexchars = "0123456789ABCDEF";
 571                  for (var i=0; i<3; i++) {
 572                      var number = Math.abs(rgb[i]);
 573                      if (number == 0 || isNaN(number)) {
 574                          hex += '00';
 575                      } else {
 576                          hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
 577                      }
 578                  }
 579                  return hex;
 580              }
 581          };
 582          /**
 583           * Initialise the colour picker :) Hoorah
 584           */
 585          colourpicker.init();
 586      });
 587  };
 588  
 589  M.util.init_block_hider = function(Y, config) {
 590      Y.use('base', 'node', function(Y) {
 591          M.util.block_hider = M.util.block_hider || (function(){
 592              var blockhider = function() {
 593                  blockhider.superclass.constructor.apply(this, arguments);
 594              };
 595              blockhider.prototype = {
 596                  initializer : function(config) {
 597                      this.set('block', '#'+this.get('id'));
 598                      var b = this.get('block'),
 599                          t = b.one('.title'),
 600                          a = null;
 601                      if (t && (a = t.one('.block_action'))) {
 602                          var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
 603                          hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
 604                          hide.on('keypress', this.updateStateKey, this, true);
 605                          var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
 606                          show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
 607                          show.on('keypress', this.updateStateKey, this, false);
 608                          a.insert(show, 0).insert(hide, 0);
 609                      }
 610                  },
 611                  updateState : function(e, hide) {
 612                      M.util.set_user_preference(this.get('preference'), hide);
 613                      if (hide) {
 614                          this.get('block').addClass('hidden');
 615                      } else {
 616                          this.get('block').removeClass('hidden');
 617                      }
 618                  },
 619                  updateStateKey : function(e, hide) {
 620                      if (e.keyCode == 13) { //allow hide/show via enter key
 621                          this.updateState(this, hide);
 622                      }
 623                  }
 624              };
 625              Y.extend(blockhider, Y.Base, blockhider.prototype, {
 626                  NAME : 'blockhider',
 627                  ATTRS : {
 628                      id : {},
 629                      preference : {},
 630                      iconVisible : {
 631                          value : M.util.image_url('t/switch_minus', 'moodle')
 632                      },
 633                      iconHidden : {
 634                          value : M.util.image_url('t/switch_plus', 'moodle')
 635                      },
 636                      block : {
 637                          setter : function(node) {
 638                              return Y.one(node);
 639                          }
 640                      }
 641                  }
 642              });
 643              return blockhider;
 644          })();
 645          new M.util.block_hider(config);
 646      });
 647  };
 648  
 649  /**
 650   * @var pending_js - The keys are the list of all pending js actions.
 651   * @type Object
 652   */
 653  M.util.pending_js = [];
 654  M.util.complete_js = [];
 655  
 656  /**
 657   * Register any long running javascript code with a unique identifier.
 658   * Should be followed with a call to js_complete with a matching
 659   * idenfitier when the code is complete. May also be called with no arguments
 660   * to test if there is any js calls pending. This is relied on by behat so that
 661   * it can wait for all pending updates before interacting with a page.
 662   * @param String uniqid - optional, if provided,
 663   *                        registers this identifier until js_complete is called.
 664   * @return boolean - True if there is any pending js.
 665   */
 666  M.util.js_pending = function(uniqid) {
 667      if (uniqid !== false) {
 668          M.util.pending_js.push(uniqid);
 669      }
 670  
 671      return M.util.pending_js.length;
 672  };
 673  
 674  // Start this asap.
 675  M.util.js_pending('init');
 676  
 677  /**
 678   * Register listeners for Y.io start/end so we can wait for them in behat.
 679   */
 680  YUI.add('moodle-core-io', function(Y) {
 681      Y.on('io:start', function(id) {
 682          M.util.js_pending('io:' + id);
 683      });
 684      Y.on('io:end', function(id) {
 685          M.util.js_complete('io:' + id);
 686      });
 687  }, '@VERSION@', {
 688      condition: {
 689          trigger: 'io-base',
 690          when: 'after'
 691      }
 692  });
 693  
 694  /**
 695   * Unregister any long running javascript code by unique identifier.
 696   * This function should form a matching pair with js_pending
 697   *
 698   * @param String uniqid - required, unregisters this identifier
 699   * @return boolean - True if there is any pending js.
 700   */
 701  M.util.js_complete = function(uniqid) {
 702      // Use the Y.Array.indexOf instead of the native because some older browsers do not support
 703      // the native function. Y.Array polyfills the native function if it does not exist.
 704      var index = Y.Array.indexOf(M.util.pending_js, uniqid);
 705      if (index >= 0) {
 706          M.util.complete_js.push(M.util.pending_js.splice(index, 1));
 707      }
 708  
 709      return M.util.pending_js.length;
 710  };
 711  
 712  /**
 713   * Returns a string registered in advance for usage in JavaScript
 714   *
 715   * If you do not pass the third parameter, the function will just return
 716   * the corresponding value from the M.str object. If the third parameter is
 717   * provided, the function performs {$a} placeholder substitution in the
 718   * same way as PHP get_string() in Moodle does.
 719   *
 720   * @param {String} identifier string identifier
 721   * @param {String} component the component providing the string
 722   * @param {Object|String} a optional variable to populate placeholder with
 723   */
 724  M.util.get_string = function(identifier, component, a) {
 725      var stringvalue;
 726  
 727      if (M.cfg.developerdebug) {
 728          // creating new instance if YUI is not optimal but it seems to be better way then
 729          // require the instance via the function API - note that it is used in rare cases
 730          // for debugging only anyway
 731          // To ensure we don't kill browser performance if hundreds of get_string requests
 732          // are made we cache the instance we generate within the M.util namespace.
 733          // We don't publicly define the variable so that it doesn't get abused.
 734          if (typeof M.util.get_string_yui_instance === 'undefined') {
 735              M.util.get_string_yui_instance = new YUI({ debug : true });
 736          }
 737          var Y = M.util.get_string_yui_instance;
 738      }
 739  
 740      if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
 741          stringvalue = '[[' + identifier + ',' + component + ']]';
 742          if (M.cfg.developerdebug) {
 743              Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
 744          }
 745          return stringvalue;
 746      }
 747  
 748      stringvalue = M.str[component][identifier];
 749  
 750      if (typeof a == 'undefined') {
 751          // no placeholder substitution requested
 752          return stringvalue;
 753      }
 754  
 755      if (typeof a == 'number' || typeof a == 'string') {
 756          // replace all occurrences of {$a} with the placeholder value
 757          stringvalue = stringvalue.replace(/\{\$a\}/g, a);
 758          return stringvalue;
 759      }
 760  
 761      if (typeof a == 'object') {
 762          // replace {$a->key} placeholders
 763          for (var key in a) {
 764              if (typeof a[key] != 'number' && typeof a[key] != 'string') {
 765                  if (M.cfg.developerdebug) {
 766                      Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
 767                  }
 768                  continue;
 769              }
 770              var search = '{$a->' + key + '}';
 771              search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
 772              search = new RegExp(search, 'g');
 773              stringvalue = stringvalue.replace(search, a[key]);
 774          }
 775          return stringvalue;
 776      }
 777  
 778      if (M.cfg.developerdebug) {
 779          Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
 780      }
 781      return stringvalue;
 782  };
 783  
 784  /**
 785   * Set focus on username or password field of the login form
 786   */
 787  M.util.focus_login_form = function(Y) {
 788      var username = Y.one('#username');
 789      var password = Y.one('#password');
 790  
 791      if (username == null || password == null) {
 792          // something is wrong here
 793          return;
 794      }
 795  
 796      var curElement = document.activeElement
 797      if (curElement == 'undefined') {
 798          // legacy browser - skip refocus protection
 799      } else if (curElement.tagName == 'INPUT') {
 800          // user was probably faster to focus something, do not mess with focus
 801          return;
 802      }
 803  
 804      if (username.get('value') == '') {
 805          username.focus();
 806      } else {
 807          password.focus();
 808      }
 809  }
 810  
 811  /**
 812   * Set focus on login error message
 813   */
 814  M.util.focus_login_error = function(Y) {
 815      var errorlog = Y.one('#loginerrormessage');
 816  
 817      if (errorlog) {
 818          errorlog.focus();
 819      }
 820  }
 821  /**
 822   * Adds lightbox hidden element that covers the whole node.
 823   *
 824   * @param {YUI} Y
 825   * @param {Node} the node lightbox should be added to
 826   * @retun {Node} created lightbox node
 827   */
 828  M.util.add_lightbox = function(Y, node) {
 829      var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
 830  
 831      // Check if lightbox is already there
 832      if (node.one('.lightbox')) {
 833          return node.one('.lightbox');
 834      }
 835  
 836      node.setStyle('position', 'relative');
 837      var waiticon = Y.Node.create('<img />')
 838      .setAttrs({
 839          'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
 840      })
 841      .setStyles({
 842          'position' : 'relative',
 843          'top' : '50%'
 844      });
 845  
 846      var lightbox = Y.Node.create('<div></div>')
 847      .setStyles({
 848          'opacity' : '.75',
 849          'position' : 'absolute',
 850          'width' : '100%',
 851          'height' : '100%',
 852          'top' : 0,
 853          'left' : 0,
 854          'backgroundColor' : 'white',
 855          'textAlign' : 'center'
 856      })
 857      .setAttribute('class', 'lightbox')
 858      .hide();
 859  
 860      lightbox.appendChild(waiticon);
 861      node.append(lightbox);
 862      return lightbox;
 863  }
 864  
 865  /**
 866   * Appends a hidden spinner element to the specified node.
 867   *
 868   * @param {YUI} Y
 869   * @param {Node} the node the spinner should be added to
 870   * @return {Node} created spinner node
 871   */
 872  M.util.add_spinner = function(Y, node) {
 873      var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
 874  
 875      // Check if spinner is already there
 876      if (node.one('.spinner')) {
 877          return node.one('.spinner');
 878      }
 879  
 880      var spinner = Y.Node.create('<img />')
 881          .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
 882          .addClass('spinner')
 883          .addClass('iconsmall')
 884          .hide();
 885  
 886      node.append(spinner);
 887      return spinner;
 888  }
 889  
 890  //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
 891  
 892  function checkall() {
 893      var inputs = document.getElementsByTagName('input');
 894      for (var i = 0; i < inputs.length; i++) {
 895          if (inputs[i].type == 'checkbox') {
 896              if (inputs[i].disabled || inputs[i].readOnly) {
 897                  continue;
 898              }
 899              inputs[i].checked = true;
 900          }
 901      }
 902  }
 903  
 904  function checknone() {
 905      var inputs = document.getElementsByTagName('input');
 906      for (var i = 0; i < inputs.length; i++) {
 907          if (inputs[i].type == 'checkbox') {
 908              if (inputs[i].disabled || inputs[i].readOnly) {
 909                  continue;
 910              }
 911              inputs[i].checked = false;
 912          }
 913      }
 914  }
 915  
 916  /**
 917   * Either check, or uncheck, all checkboxes inside the element with id is
 918   * @param id the id of the container
 919   * @param checked the new state, either '' or 'checked'.
 920   */
 921  function select_all_in_element_with_id(id, checked) {
 922      var container = document.getElementById(id);
 923      if (!container) {
 924          return;
 925      }
 926      var inputs = container.getElementsByTagName('input');
 927      for (var i = 0; i < inputs.length; ++i) {
 928          if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
 929              inputs[i].checked = checked;
 930          }
 931      }
 932  }
 933  
 934  function select_all_in(elTagName, elClass, elId) {
 935      var inputs = document.getElementsByTagName('input');
 936      inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
 937      for(var i = 0; i < inputs.length; ++i) {
 938          if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
 939              inputs[i].checked = 'checked';
 940          }
 941      }
 942  }
 943  
 944  function deselect_all_in(elTagName, elClass, elId) {
 945      var inputs = document.getElementsByTagName('INPUT');
 946      inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
 947      for(var i = 0; i < inputs.length; ++i) {
 948          if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
 949              inputs[i].checked = '';
 950          }
 951      }
 952  }
 953  
 954  function confirm_if(expr, message) {
 955      if(!expr) {
 956          return true;
 957      }
 958      return confirm(message);
 959  }
 960  
 961  
 962  /*
 963      findParentNode (start, elementName, elementClass, elementID)
 964  
 965      Travels up the DOM hierarchy to find a parent element with the
 966      specified tag name, class, and id. All conditions must be met,
 967      but any can be ommitted. Returns the BODY element if no match
 968      found.
 969  */
 970  function findParentNode(el, elName, elClass, elId) {
 971      while (el.nodeName.toUpperCase() != 'BODY') {
 972          if ((!elName || el.nodeName.toUpperCase() == elName) &&
 973              (!elClass || el.className.indexOf(elClass) != -1) &&
 974              (!elId || el.id == elId)) {
 975              break;
 976          }
 977          el = el.parentNode;
 978      }
 979      return el;
 980  }
 981  /*
 982      findChildNode (start, elementName, elementClass, elementID)
 983  
 984      Travels down the DOM hierarchy to find all child elements with the
 985      specified tag name, class, and id. All conditions must be met,
 986      but any can be ommitted.
 987      Doesn't examine children of matches.
 988  
 989      @deprecated since Moodle 2.7 - please do not use this function any more.
 990      @todo MDL-43242 This will be deleted in Moodle 2.9.
 991      @see Y.all
 992  */
 993  function findChildNodes(start, tagName, elementClass, elementID, elementName) {
 994      Y.log("findChildNodes() is deprecated. Please use Y.all instead.",
 995              "warn", "javascript-static.js");
 996      var children = new Array();
 997      for (var i = 0; i < start.childNodes.length; i++) {
 998          var classfound = false;
 999          var child = start.childNodes[i];
1000          if((child.nodeType == 1) &&//element node type
1001                    (elementClass && (typeof(child.className)=='string'))) {
1002              var childClasses = child.className.split(/\s+/);
1003              for (var childClassIndex in childClasses) {
1004                  if (childClasses[childClassIndex]==elementClass) {
1005                      classfound = true;
1006                      break;
1007                  }
1008              }
1009          }
1010          if(child.nodeType == 1) { //element node type
1011              if  ( (!tagName || child.nodeName == tagName) &&
1012                  (!elementClass || classfound)&&
1013                  (!elementID || child.id == elementID) &&
1014                  (!elementName || child.name == elementName))
1015              {
1016                  children = children.concat(child);
1017              } else {
1018                  children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1019              }
1020          }
1021      }
1022      return children;
1023  }
1024  
1025  function unmaskPassword(id) {
1026      var pw = document.getElementById(id);
1027      var chb = document.getElementById(id+'unmask');
1028  
1029      // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower.
1030      // Replacing existing child with a new one, removed all yui properties for the node.  Therefore, this
1031      // functionality won't work in IE8 or lower.
1032      // This is a temporary fixed to allow other browsers to function properly.
1033      if (Y.UA.ie == 0 || Y.UA.ie >= 9) {
1034          if (chb.checked) {
1035              pw.type = "text";
1036          } else {
1037              pw.type = "password";
1038          }
1039      } else {  //IE Browser version 8 or lower
1040          try {
1041              // first try IE way - it can not set name attribute later
1042              if (chb.checked) {
1043                var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1044              } else {
1045                var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1046              }
1047              newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1048          } catch (e) {
1049              var newpw = document.createElement('input');
1050              newpw.setAttribute('autocomplete', 'off');
1051              newpw.setAttribute('name', pw.name);
1052              if (chb.checked) {
1053                newpw.setAttribute('type', 'text');
1054              } else {
1055                newpw.setAttribute('type', 'password');
1056              }
1057              newpw.setAttribute('class', pw.getAttribute('class'));
1058          }
1059          newpw.id = pw.id;
1060          newpw.size = pw.size;
1061          newpw.onblur = pw.onblur;
1062          newpw.onchange = pw.onchange;
1063          newpw.value = pw.value;
1064          pw.parentNode.replaceChild(newpw, pw);
1065      }
1066  }
1067  
1068  function filterByParent(elCollection, parentFinder) {
1069      var filteredCollection = [];
1070      for (var i = 0; i < elCollection.length; ++i) {
1071          var findParent = parentFinder(elCollection[i]);
1072          if (findParent.nodeName.toUpperCase() != 'BODY') {
1073              filteredCollection.push(elCollection[i]);
1074          }
1075      }
1076      return filteredCollection;
1077  }
1078  
1079  /*
1080      All this is here just so that IE gets to handle oversized blocks
1081      in a visually pleasing manner. It does a browser detect. So sue me.
1082  */
1083  
1084  function fix_column_widths() {
1085      var agt = navigator.userAgent.toLowerCase();
1086      if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1087          fix_column_width('left-column');
1088          fix_column_width('right-column');
1089      }
1090  }
1091  
1092  function fix_column_width(colName) {
1093      if(column = document.getElementById(colName)) {
1094          if(!column.offsetWidth) {
1095              setTimeout("fix_column_width('" + colName + "')", 20);
1096              return;
1097          }
1098  
1099          var width = 0;
1100          var nodes = column.childNodes;
1101  
1102          for(i = 0; i < nodes.length; ++i) {
1103              if(nodes[i].className.indexOf("block") != -1 ) {
1104                  if(width < nodes[i].offsetWidth) {
1105                      width = nodes[i].offsetWidth;
1106                  }
1107              }
1108          }
1109  
1110          for(i = 0; i < nodes.length; ++i) {
1111              if(nodes[i].className.indexOf("block") != -1 ) {
1112                  nodes[i].style.width = width + 'px';
1113              }
1114          }
1115      }
1116  }
1117  
1118  
1119  /*
1120     Insert myValue at current cursor position
1121   */
1122  function insertAtCursor(myField, myValue) {
1123      // IE support
1124      if (document.selection) {
1125          myField.focus();
1126          sel = document.selection.createRange();
1127          sel.text = myValue;
1128      }
1129      // Mozilla/Netscape support
1130      else if (myField.selectionStart || myField.selectionStart == '0') {
1131          var startPos = myField.selectionStart;
1132          var endPos = myField.selectionEnd;
1133          myField.value = myField.value.substring(0, startPos)
1134              + myValue + myField.value.substring(endPos, myField.value.length);
1135      } else {
1136          myField.value += myValue;
1137      }
1138  }
1139  
1140  
1141  /*
1142          Call instead of setting window.onload directly or setting body onload=.
1143          Adds your function to a chain of functions rather than overwriting anything
1144          that exists.
1145          @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
1146  */
1147  function addonload(fn) {
1148      Y.log('addonload has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1149              'warn', 'javascript-static.js');
1150      var oldhandler=window.onload;
1151      window.onload=function() {
1152          if(oldhandler) oldhandler();
1153              fn();
1154      }
1155  }
1156  /**
1157   * Replacement for getElementsByClassName in browsers that aren't cool enough
1158   *
1159   * Relying on the built-in getElementsByClassName is far, far faster than
1160   * using YUI.
1161   *
1162   * Note: the third argument used to be an object with odd behaviour. It now
1163   * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1164   * mimicked if you pass an object.
1165   *
1166   * @param {Node} oElm The top-level node for searching. To search a whole
1167   *                    document, use `document`.
1168   * @param {String} strTagName filter by tag names
1169   * @param {String} name same as HTML5 spec
1170   * @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
1171   */
1172  function getElementsByClassName(oElm, strTagName, name) {
1173      Y.log('getElementsByClassName has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1174              'warn', 'javascript-static.js');
1175      // for backwards compatibility
1176      if(typeof name == "object") {
1177          var names = new Array();
1178          for(var i=0; i<name.length; i++) names.push(names[i]);
1179          name = names.join('');
1180      }
1181      // use native implementation if possible
1182      if (oElm.getElementsByClassName && Array.filter) {
1183          if (strTagName == '*') {
1184              return oElm.getElementsByClassName(name);
1185          } else {
1186              return Array.filter(oElm.getElementsByClassName(name), function(el) {
1187                  return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1188              });
1189          }
1190      }
1191      // native implementation unavailable, fall back to slow method
1192      var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1193      var arrReturnElements = new Array();
1194      var arrRegExpClassNames = new Array();
1195      var names = name.split(' ');
1196      for(var i=0; i<names.length; i++) {
1197          arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1198      }
1199      var oElement;
1200      var bMatchesAll;
1201      for(var j=0; j<arrElements.length; j++) {
1202          oElement = arrElements[j];
1203          bMatchesAll = true;
1204          for(var k=0; k<arrRegExpClassNames.length; k++) {
1205              if(!arrRegExpClassNames[k].test(oElement.className)) {
1206                  bMatchesAll = false;
1207                  break;
1208              }
1209          }
1210          if(bMatchesAll) {
1211              arrReturnElements.push(oElement);
1212          }
1213      }
1214      return (arrReturnElements)
1215  }
1216  
1217  /**
1218   * Increment a file name.
1219   *
1220   * @param string file name.
1221   * @param boolean ignoreextension do not extract the extension prior to appending the
1222   *                                suffix. Useful when incrementing folder names.
1223   * @return string the incremented file name.
1224   */
1225  function increment_filename(filename, ignoreextension) {
1226      var extension = '';
1227      var basename = filename;
1228  
1229      // Split the file name into the basename + extension.
1230      if (!ignoreextension) {
1231          var dotpos = filename.lastIndexOf('.');
1232          if (dotpos !== -1) {
1233              basename = filename.substr(0, dotpos);
1234              extension = filename.substr(dotpos, filename.length);
1235          }
1236      }
1237  
1238      // Look to see if the name already has (NN) at the end of it.
1239      var number = 0;
1240      var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
1241      if (hasnumber !== null) {
1242          // Note the current number & remove it from the basename.
1243          number = parseInt(hasnumber[2], 10);
1244          basename = hasnumber[1];
1245      }
1246  
1247      number++;
1248      var newname = basename + ' (' + number + ')' + extension;
1249      return newname;
1250  }
1251  
1252  /**
1253   * Return whether we are in right to left mode or not.
1254   *
1255   * @return boolean
1256   */
1257  function right_to_left() {
1258      var body = Y.one('body');
1259      var rtl = false;
1260      if (body && body.hasClass('dir-rtl')) {
1261          rtl = true;
1262      }
1263      return rtl;
1264  }
1265  
1266  function openpopup(event, args) {
1267  
1268      if (event) {
1269          if (event.preventDefault) {
1270              event.preventDefault();
1271          } else {
1272              event.returnValue = false;
1273          }
1274      }
1275  
1276      // Make sure the name argument is set and valid.
1277      var nameregex = /[^a-z0-9_]/i;
1278      if (typeof args.name !== 'string') {
1279          args.name = '_blank';
1280      } else if (args.name.match(nameregex)) {
1281          // Cleans window name because IE does not support funky ones.
1282          if (M.cfg.developerdebug) {
1283              alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name);
1284          }
1285          args.name = args.name.replace(nameregex, '_');
1286      }
1287  
1288      var fullurl = args.url;
1289      if (!args.url.match(/https?:\/\//)) {
1290          fullurl = M.cfg.wwwroot + args.url;
1291      }
1292      if (args.fullscreen) {
1293          args.options = args.options.
1294                  replace(/top=\d+/, 'top=0').
1295                  replace(/left=\d+/, 'left=0').
1296                  replace(/width=\d+/, 'width=' + screen.availWidth).
1297                  replace(/height=\d+/, 'height=' + screen.availHeight);
1298      }
1299      var windowobj = window.open(fullurl,args.name,args.options);
1300      if (!windowobj) {
1301          return true;
1302      }
1303  
1304      if (args.fullscreen) {
1305          // In some browser / OS combinations (E.g. Chrome on Windows), the
1306          // window initially opens slighly too big. The width and heigh options
1307          // seem to control the area inside the browser window, so what with
1308          // scroll-bars, etc. the actual window is bigger than the screen.
1309          // Therefore, we need to fix things up after the window is open.
1310          var hackcount = 100;
1311          var get_size_exactly_right = function() {
1312              windowobj.moveTo(0, 0);
1313              windowobj.resizeTo(screen.availWidth, screen.availHeight);
1314  
1315              // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1316              // something like windowobj.resizeTo(1280, 1024) too soon (up to
1317              // about 50ms) after the window is open, then it actually behaves
1318              // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1319              // check that the resize actually worked, and if not, repeatedly try
1320              // again after a short delay until it works (but with a limit of
1321              // hackcount repeats.
1322              if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1323                  hackcount -= 1;
1324                  setTimeout(get_size_exactly_right, 10);
1325              }
1326          }
1327          setTimeout(get_size_exactly_right, 0);
1328      }
1329      windowobj.focus();
1330  
1331      return false;
1332  }
1333  
1334  /** Close the current browser window. */
1335  function close_window(e) {
1336      if (e.preventDefault) {
1337          e.preventDefault();
1338      } else {
1339          e.returnValue = false;
1340      }
1341      window.close();
1342  }
1343  
1344  /**
1345   * Used in a couple of modules to hide navigation areas when using AJAX
1346   * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1347   */
1348  function show_item(itemid) {
1349      Y.log('show_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1350              'warn', 'javascript-static.js');
1351      var item = Y.one('#' + itemid);
1352      if (item) {
1353          item.show();
1354      }
1355  }
1356  
1357  // Deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1358  function destroy_item(itemid) {
1359      Y.log('destroy_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1360              'warn', 'javascript-static.js');
1361      var item = Y.one('#' + itemid);
1362      if (item) {
1363          item.remove(true);
1364      }
1365  }
1366  /**
1367   * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1368   * @param controlid the control id.
1369   */
1370  function focuscontrol(controlid) {
1371      var control = document.getElementById(controlid);
1372      if (control) {
1373          control.focus();
1374      }
1375  }
1376  
1377  /**
1378   * Transfers keyboard focus to an HTML element based on the old style style of focus
1379   * This function should be removed as soon as it is no longer used
1380   */
1381  function old_onload_focus(formid, controlname) {
1382      if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1383          document.forms[formid].elements[controlname].focus();
1384      }
1385  }
1386  
1387  function build_querystring(obj) {
1388      return convert_object_to_string(obj, '&');
1389  }
1390  
1391  function build_windowoptionsstring(obj) {
1392      return convert_object_to_string(obj, ',');
1393  }
1394  
1395  function convert_object_to_string(obj, separator) {
1396      if (typeof obj !== 'object') {
1397          return null;
1398      }
1399      var list = [];
1400      for(var k in obj) {
1401          k = encodeURIComponent(k);
1402          var value = obj[k];
1403          if(obj[k] instanceof Array) {
1404              for(var i in value) {
1405                  list.push(k+'[]='+encodeURIComponent(value[i]));
1406              }
1407          } else {
1408              list.push(k+'='+encodeURIComponent(value));
1409          }
1410      }
1411      return list.join(separator);
1412  }
1413  
1414  function stripHTML(str) {
1415      var re = /<\S[^><]*>/g;
1416      var ret = str.replace(re, "");
1417      return ret;
1418  }
1419  
1420  function updateProgressBar(id, percent, msg, estimate) {
1421      var progressIndicator = Y.one('#' + id);
1422      if (!progressIndicator) {
1423          return;
1424      }
1425  
1426      var progressBar = progressIndicator.one('.bar'),
1427          statusIndicator = progressIndicator.one('h2'),
1428          estimateIndicator = progressIndicator.one('p');
1429  
1430      statusIndicator.set('innerHTML', Y.Escape.html(msg));
1431      progressBar.set('innerHTML', Y.Escape.html('' + percent + '%'));
1432      if (percent === 100) {
1433          progressIndicator.addClass('progress-success');
1434          estimateIndicator.set('innerHTML', null);
1435      } else {
1436          if (estimate) {
1437              estimateIndicator.set('innerHTML', Y.Escape.html(estimate));
1438          } else {
1439              estimateIndicator.set('innerHTML', null);
1440          }
1441          progressIndicator.removeClass('progress-success');
1442      }
1443      progressBar.setAttribute('aria-valuenow', percent);
1444      progressBar.setStyle('width', percent + '%');
1445  }
1446  
1447  // ===== Deprecated core Javascript functions for Moodle ====
1448  //       DO NOT USE!!!!!!!
1449  // Do not put this stuff in separate file because it only adds extra load on servers!
1450  
1451  /**
1452   * Used in a couple of modules to hide navigation areas when using AJAX
1453   * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1454   */
1455  function hide_item(itemid) {
1456      Y.log('hide_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1457              'warn', 'javascript-static.js');
1458      var item = Y.one('#' + itemid);
1459      if (item) {
1460          item.hide();
1461      }
1462  }
1463  
1464  M.util.help_popups = {
1465      setup : function(Y) {
1466          Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1467      },
1468      open_popup : function(e) {
1469          // Prevent the default page action
1470          e.preventDefault();
1471  
1472          // Grab the anchor that was clicked
1473          var anchor = e.target.ancestor('a', true);
1474          var args = {
1475              'name'          : 'popup',
1476              'url'           : anchor.getAttribute('href'),
1477              'options'       : ''
1478          };
1479          var options = [
1480              'height=600',
1481              'width=800',
1482              'top=0',
1483              'left=0',
1484              'menubar=0',
1485              'location=0',
1486              'scrollbars',
1487              'resizable',
1488              'toolbar',
1489              'status',
1490              'directories=0',
1491              'fullscreen=0',
1492              'dependent'
1493          ]
1494          args.options = options.join(',');
1495  
1496          openpopup(e, args);
1497      }
1498  }
1499  
1500  /**
1501   * Custom menu namespace
1502   */
1503  M.core_custom_menu = {
1504      /**
1505       * This method is used to initialise a custom menu given the id that belongs
1506       * to the custom menu's root node.
1507       *
1508       * @param {YUI} Y
1509       * @param {string} nodeid
1510       */
1511      init : function(Y, nodeid) {
1512          var node = Y.one('#'+nodeid);
1513          if (node) {
1514              Y.use('node-menunav', function(Y) {
1515                  // Get the node
1516                  // Remove the javascript-disabled class.... obviously javascript is enabled.
1517                  node.removeClass('javascript-disabled');
1518                  // Initialise the menunav plugin
1519                  node.plug(Y.Plugin.NodeMenuNav);
1520              });
1521          }
1522      }
1523  };
1524  
1525  /**
1526   * Used to store form manipulation methods and enhancments
1527   */
1528  M.form = M.form || {};
1529  
1530  /**
1531   * Converts a nbsp indented select box into a multi drop down custom control much
1532   * like the custom menu. It also selectable categories on or off.
1533   *
1534   * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1535   *
1536   * @param {YUI} Y
1537   * @param {string} id
1538   * @param {Array} options
1539   */
1540  M.form.init_smartselect = function(Y, id, options) {
1541      if (!id.match(/^id_/)) {
1542          id = 'id_'+id;
1543      }
1544      var select = Y.one('select#'+id);
1545      if (!select) {
1546          return false;
1547      }
1548      Y.use('event-delegate',function(){
1549          var smartselect = {
1550              id : id,
1551              structure : [],
1552              options : [],
1553              submenucount : 0,
1554              currentvalue : null,
1555              currenttext : null,
1556              shownevent : null,
1557              cfg : {
1558                  selectablecategories : true,
1559                  mode : null
1560              },
1561              nodes : {
1562                  select : null,
1563                  loading : null,
1564                  menu : null
1565              },
1566              init : function(Y, id, args, nodes) {
1567                  if (typeof(args)=='object') {
1568                      for (var i in this.cfg) {
1569                          if (args[i] || args[i]===false) {
1570                              this.cfg[i] = args[i];
1571                          }
1572                      }
1573                  }
1574  
1575                  // Display a loading message first up
1576                  this.nodes.select = nodes.select;
1577  
1578                  this.currentvalue = this.nodes.select.get('selectedIndex');
1579                  this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1580  
1581                  var options = Array();
1582                  options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1583                  this.nodes.select.all('option').each(function(option, index) {
1584                      var rawtext = option.get('innerHTML');
1585                      var text = rawtext.replace(/^(&nbsp;)*/, '');
1586                      if (rawtext === text) {
1587                          text = rawtext.replace(/^(\s)*/, '');
1588                          var depth = (rawtext.length - text.length ) + 1;
1589                      } else {
1590                          var depth = ((rawtext.length - text.length )/12)+1;
1591                      }
1592                      option.set('innerHTML', text);
1593                      options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1594                  }, this);
1595  
1596                  this.structure = [];
1597                  var structcount = 0;
1598                  for (var i in options) {
1599                      var o = options[i];
1600                      if (o.depth == 0) {
1601                          this.structure.push(o);
1602                          structcount++;
1603                      } else {
1604                          var d = o.depth;
1605                          var current = this.structure[structcount-1];
1606                          for (var j = 0; j < o.depth-1;j++) {
1607                              if (current && current.children) {
1608                                  current = current.children[current.children.length-1];
1609                              }
1610                          }
1611                          if (current && current.children) {
1612                              current.children.push(o);
1613                          }
1614                      }
1615                  }
1616  
1617                  this.nodes.menu = Y.Node.create(this.generate_menu_content());
1618                  this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1619                  this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1620                  this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1621  
1622                  if (this.cfg.mode == null) {
1623                      var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1624                      if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1625                          this.cfg.mode = 'compact';
1626                      } else {
1627                          this.cfg.mode = 'spanning';
1628                      }
1629                  }
1630  
1631                  if (this.cfg.mode == 'compact') {
1632                      this.nodes.menu.addClass('compactmenu');
1633                  } else {
1634                      this.nodes.menu.addClass('spanningmenu');
1635                      this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1636                  }
1637  
1638                  Y.one(document.body).append(this.nodes.menu);
1639                  var pos = this.nodes.select.getXY();
1640                  pos[0] += 1;
1641                  this.nodes.menu.setXY(pos);
1642                  this.nodes.menu.on('click', this.handle_click, this);
1643  
1644                  Y.one(window).on('resize', function(){
1645                       var pos = this.nodes.select.getXY();
1646                      pos[0] += 1;
1647                      this.nodes.menu.setXY(pos);
1648                   }, this);
1649              },
1650              generate_menu_content : function() {
1651                  var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1652                  content += this.generate_submenu_content(this.structure[0], true);
1653                  content += '</ul></div>';
1654                  return content;
1655              },
1656              generate_submenu_content : function(item, rootelement) {
1657                  this.submenucount++;
1658                  var content = '';
1659                  if (item.children.length > 0) {
1660                      if (rootelement) {
1661                          content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1662                          content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1663                          content += '<div class="smartselect_menu_content">';
1664                      } else {
1665                          content += '<li class="smartselect_submenuitem">';
1666                          var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1667                          content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1668                          content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1669                          content += '<div class="smartselect_submenu_content">';
1670                      }
1671                      content += '<ul>';
1672                      for (var i in item.children) {
1673                          content += this.generate_submenu_content(item.children[i],false);
1674                      }
1675                      content += '</ul>';
1676                      content += '</div>';
1677                      content += '</div>';
1678                      if (rootelement) {
1679                      } else {
1680                          content += '</li>';
1681                      }
1682                  } else {
1683                      content += '<li class="smartselect_menuitem">';
1684                      content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1685                      content += '</li>';
1686                  }
1687                  return content;
1688              },
1689              select : function(e) {
1690                  var t = e.target;
1691                  e.halt();
1692                  this.currenttext = t.get('innerHTML');
1693                  this.currentvalue = t.getAttribute('value');
1694                  this.nodes.select.set('selectedIndex', this.currentvalue);
1695                  this.hide_menu();
1696              },
1697              handle_click : function(e) {
1698                  var target = e.target;
1699                  if (target.hasClass('smartselect_mask')) {
1700                      this.show_menu(e);
1701                  } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1702                      this.select(e);
1703                  } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1704                      this.show_sub_menu(e);
1705                  }
1706              },
1707              show_menu : function(e) {
1708                  e.halt();
1709                  var menu = e.target.ancestor().one('.smartselect_menu');
1710                  menu.addClass('visible');
1711                  this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1712              },
1713              show_sub_menu : function(e) {
1714                  e.halt();
1715                  var target = e.target;
1716                  if (!target.hasClass('smartselect_submenuitem')) {
1717                      target = target.ancestor('.smartselect_submenuitem');
1718                  }
1719                  if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1720                      target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1721                      return;
1722                  }
1723                  target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1724                  target.one('.smartselect_submenu').addClass('visible');
1725              },
1726              hide_menu : function() {
1727                  this.nodes.menu.all('.visible').removeClass('visible');
1728                  if (this.shownevent) {
1729                      this.shownevent.detach();
1730                  }
1731              }
1732          };
1733          smartselect.init(Y, id, options, {select:select});
1734      });
1735  };
1736  
1737  /** List of flv players to be loaded */
1738  M.util.video_players = [];
1739  /** List of mp3 players to be loaded */
1740  M.util.audio_players = [];
1741  
1742  /**
1743   * Add video player
1744   * @param id element id
1745   * @param fileurl media url
1746   * @param width
1747   * @param height
1748   * @param autosize true means detect size from media
1749   */
1750  M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1751      M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1752  };
1753  
1754  /**
1755   * Add audio player.
1756   * @param id
1757   * @param fileurl
1758   * @param small
1759   */
1760  M.util.add_audio_player = function (id, fileurl, small) {
1761      M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1762  };
1763  
1764  /**
1765   * Initialise all audio and video player, must be called from page footer.
1766   */
1767  M.util.load_flowplayer = function() {
1768      if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1769          return;
1770      }
1771      if (typeof(flowplayer) == 'undefined') {
1772          var loaded = false;
1773  
1774          var embed_function = function() {
1775              if (loaded || typeof(flowplayer) == 'undefined') {
1776                  return;
1777              }
1778              loaded = true;
1779  
1780              var controls = {
1781                      autoHide: true
1782              }
1783              /* TODO: add CSS color overrides for the flv flow player */
1784  
1785              for(var i=0; i<M.util.video_players.length; i++) {
1786                  var video = M.util.video_players[i];
1787                  if (video.width > 0 && video.height > 0) {
1788                      var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', width: video.width, height: video.height};
1789                  } else {
1790                      var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf';
1791                  }
1792                  flowplayer(video.id, src, {
1793                      plugins: {controls: controls},
1794                      clip: {
1795                          url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1796                          onMetaData: function(clip) {
1797                              if (clip.mvideo.autosize && !clip.mvideo.resized) {
1798                                  clip.mvideo.resized = true;
1799                                  //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1800                                  if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1801                                      // bad luck, we have to guess - we may not get metadata at all
1802                                      var width = clip.width;
1803                                      var height = clip.height;
1804                                  } else {
1805                                      var width = clip.metaData.width;
1806                                      var height = clip.metaData.height;
1807                                  }
1808                                  var minwidth = 300; // controls are messed up in smaller objects
1809                                  if (width < minwidth) {
1810                                      height = (height * minwidth) / width;
1811                                      width = minwidth;
1812                                  }
1813  
1814                                  var object = this._api();
1815                                  object.width = width;
1816                                  object.height = height;
1817                              }
1818                          }
1819                      }
1820                  });
1821              }
1822              if (M.util.audio_players.length == 0) {
1823                  return;
1824              }
1825              var controls = {
1826                      autoHide: false,
1827                      fullscreen: false,
1828                      next: false,
1829                      previous: false,
1830                      scrubber: true,
1831                      play: true,
1832                      pause: true,
1833                      volume: true,
1834                      mute: false,
1835                      backgroundGradient: [0.5,0,0.3]
1836                  };
1837  
1838              var rule;
1839              for (var j=0; j < document.styleSheets.length; j++) {
1840  
1841                  // To avoid javascript security violation accessing cross domain stylesheets
1842                  var allrules = false;
1843                  try {
1844                      if (typeof (document.styleSheets[j].rules) != 'undefined') {
1845                          allrules = document.styleSheets[j].rules;
1846                      } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1847                          allrules = document.styleSheets[j].cssRules;
1848                      } else {
1849                          // why??
1850                          continue;
1851                      }
1852                  } catch (e) {
1853                      continue;
1854                  }
1855  
1856                  // On cross domain style sheets Chrome V8 allows access to rules but returns null
1857                  if (!allrules) {
1858                      continue;
1859                  }
1860  
1861                  for(var i=0; i<allrules.length; i++) {
1862                      rule = '';
1863                      if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1864                          if (typeof(allrules[i].cssText) != 'undefined') {
1865                              rule = allrules[i].cssText;
1866                          } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1867                              rule = allrules[i].style.cssText;
1868                          }
1869                          if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1870                              rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1871                              var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1872                              controls[colprop] = rule;
1873                          }
1874                      }
1875                  }
1876                  allrules = false;
1877              }
1878  
1879              for(i=0; i<M.util.audio_players.length; i++) {
1880                  var audio = M.util.audio_players[i];
1881                  if (audio.small) {
1882                      controls.controlall = false;
1883                      controls.height = 15;
1884                      controls.time = false;
1885                  } else {
1886                      controls.controlall = true;
1887                      controls.height = 25;
1888                      controls.time = true;
1889                  }
1890                  flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', {
1891                      plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf'}},
1892                      clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1893                  });
1894              }
1895          }
1896  
1897          if (M.cfg.jsrev == -1) {
1898              var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.13.js';
1899          } else {
1900              var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.13.min.js&rev=' + M.cfg.jsrev;
1901          }
1902          var fileref = document.createElement('script');
1903          fileref.setAttribute('type','text/javascript');
1904          fileref.setAttribute('src', jsurl);
1905          fileref.onload = embed_function;
1906          fileref.onreadystatechange = embed_function;
1907          document.getElementsByTagName('head')[0].appendChild(fileref);
1908      }
1909  };


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1