[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yui/build/moodle-core-dock/ -> moodle-core-dock.js (source)

   1  YUI.add('moodle-core-dock', function (Y, NAME) {
   2  
   3  /**
   4   * Dock JS.
   5   *
   6   * This file contains the DOCK object and all dock related global namespace methods and properties.
   7   *
   8   * @module moodle-core-dock
   9   */
  10  
  11  var LOGNS = 'moodle-core-dock',
  12      BODY = Y.one(Y.config.doc.body),
  13      CSS = {
  14          dock: 'dock',                    // CSS Class applied to the dock box
  15          dockspacer: 'dockspacer',        // CSS class applied to the dockspacer
  16          controls: 'controls',            // CSS class applied to the controls box
  17          body: 'has_dock',                // CSS class added to the body when there is a dock
  18          buttonscontainer: 'buttons_container',
  19          dockeditem: 'dockeditem',        // CSS class added to each item in the dock
  20          dockeditemcontainer: 'dockeditem_container',
  21          dockedtitle: 'dockedtitle',      // CSS class added to the item's title in each dock
  22          activeitem: 'activeitem',        // CSS class added to the active item
  23          dockonload: 'dock_on_load'
  24      },
  25      SELECTOR = {
  26          dockableblock: '.block[data-instanceid][data-dockable]',
  27          blockmoveto: '.block[data-instanceid][data-dockable] .moveto',
  28          panelmoveto: '#dockeditempanel .commands a.moveto',
  29          dockonload: '.block.' + CSS.dockonload,
  30          blockregion: '[data-blockregion]'
  31      },
  32      DOCK,
  33      DOCKPANEL,
  34      TABHEIGHTMANAGER,
  35      BLOCK,
  36      DOCKEDITEM; // eslint-disable-line no-unused-vars
  37  
  38  M.core = M.core || {};
  39  M.core.dock = M.core.dock || {};
  40  
  41  /**
  42   * The dock - once initialised.
  43   *
  44   * @private
  45   * @property _dock
  46   * @type DOCK
  47   */
  48  M.core.dock._dock = null;
  49  
  50  /**
  51   * An associative array of dockable blocks.
  52   * @property _dockableblocks
  53   * @type {Array} An array of BLOCK objects organised by instanceid.
  54   * @private
  55   */
  56  M.core.dock._dockableblocks = {};
  57  
  58  /**
  59   * Initialises the dock.
  60   * This method registers dockable blocks, and creates delegations to dock them.
  61   * @static
  62   * @method init
  63   */
  64  M.core.dock.init = function() {
  65      Y.all(SELECTOR.dockableblock).each(M.core.dock.registerDockableBlock);
  66      Y.Global.on(M.core.globalEvents.BLOCK_CONTENT_UPDATED, function(e) {
  67          M.core.dock.notifyBlockChange(e.instanceid);
  68      }, this);
  69      BODY.delegate('click', M.core.dock.dockBlock, SELECTOR.blockmoveto);
  70      BODY.delegate('key', M.core.dock.dockBlock, SELECTOR.blockmoveto, 'enter');
  71  };
  72  
  73  /**
  74   * Returns an instance of the dock.
  75   * Initialises one if one hasn't already being initialised.
  76   *
  77   * @static
  78   * @method get
  79   * @return DOCK
  80   */
  81  M.core.dock.get = function() {
  82      if (this._dock === null) {
  83          this._dock = new DOCK();
  84      }
  85      return this._dock;
  86  };
  87  
  88  /**
  89   * Registers a dockable block with the dock.
  90   *
  91   * @static
  92   * @method registerDockableBlock
  93   * @param {int} id The block instance ID.
  94   * @return void
  95   */
  96  M.core.dock.registerDockableBlock = function(id) {
  97      if (typeof id === 'object' && typeof id.getData === 'function') {
  98          id = id.getData('instanceid');
  99      }
 100      M.core.dock._dockableblocks[id] = new BLOCK({id: id});
 101  };
 102  
 103  /**
 104   * Docks a block given either its instanceid, its node, or an event fired from within the block.
 105   * @static
 106   * @method dockBlockByInstanceID
 107   * @param id
 108   * @return void
 109   */
 110  M.core.dock.dockBlock = function(id) {
 111      if (typeof id === 'object' && id.target !== 'undefined') {
 112          id = id.target;
 113      }
 114      if (typeof id === "object") {
 115          if (!id.test(SELECTOR.dockableblock)) {
 116              id = id.ancestor(SELECTOR.dockableblock);
 117          }
 118          if (typeof id === 'object' && typeof id.getData === 'function' && !id.ancestor('.' + CSS.dock)) {
 119              id = id.getData('instanceid');
 120          } else {
 121              return;
 122          }
 123      }
 124      var block = M.core.dock._dockableblocks[id];
 125      if (block) {
 126          block.moveToDock();
 127      }
 128  };
 129  
 130  /**
 131   * Fixes the title orientation. Rotating it if required.
 132   *
 133   * @static
 134   * @method fixTitleOrientation
 135   * @param {Node} title The title node we are looking at.
 136   * @param {String} text The string to use as the title.
 137   * @return {Node} The title node to use.
 138   */
 139  M.core.dock.fixTitleOrientation = function(title, text) {
 140      var dock = M.core.dock.get(),
 141          fontsize = '11px',
 142          transform = 'rotate(270deg)',
 143          test,
 144          width,
 145          height,
 146          container,
 147          verticaldirection = M.util.get_string('thisdirectionvertical', 'langconfig');
 148      title = Y.one(title);
 149  
 150      if (dock.get('orientation') !== 'vertical') {
 151          // If the dock isn't vertical don't adjust it!
 152          title.set('innerHTML', text);
 153          return title;
 154      }
 155  
 156      if (Y.UA.ie > 0 && Y.UA.ie < 8) {
 157          // IE 6/7 can't rotate text so force ver
 158          verticaldirection = 'ver';
 159      }
 160  
 161      switch (verticaldirection) {
 162          case 'ver':
 163              // Stacked is easy
 164              return title.set('innerHTML', text.split('').join('<br />'));
 165          case 'ttb':
 166              transform = 'rotate(90deg)';
 167              break;
 168          case 'btt':
 169              // Nothing to do here. transform default is good.
 170              break;
 171      }
 172  
 173      if (Y.UA.ie === 8) {
 174          // IE8 can flip the text via CSS but not handle transform. IE9+ can handle the CSS3 transform attribute.
 175          title.set('innerHTML', text);
 176          title.setAttribute('style', 'writing-mode: tb-rl; filter: flipV flipH;display:inline;');
 177          title.addClass('filterrotate');
 178          return title;
 179      }
 180  
 181      // We need to fix a font-size - sorry theme designers.
 182      test = Y.Node.create('<h2 class="transform-test-heading"><span class="transform-test-node" style="font-size:' +
 183              fontsize + ';">' + text + '</span></h2>');
 184      BODY.insert(test, 0);
 185      width = test.one('span').get('offsetWidth') * 1.2;
 186      height = test.one('span').get('offsetHeight');
 187      test.remove();
 188  
 189      title.set('innerHTML', text);
 190      title.addClass('css3transform');
 191  
 192      // Move the title into position
 193      title.setStyles({
 194          'position': 'relative',
 195          'fontSize': fontsize,
 196          'width': width,
 197          'top': (width - height) / 2
 198      });
 199  
 200      // Positioning is different when in RTL mode.
 201      if (window.right_to_left()) {
 202          title.setStyle('left', width / 2 - height);
 203      } else {
 204          title.setStyle('right', width / 2 - height);
 205      }
 206  
 207      // Rotate the text
 208      title.setStyles({
 209          'transform': transform,
 210          '-ms-transform': transform,
 211          '-moz-transform': transform,
 212          '-webkit-transform': transform,
 213          '-o-transform': transform
 214      });
 215  
 216      container = Y.Node.create('<div></div>');
 217      container.append(title);
 218      container.setStyles({
 219          height: width + (width / 4),
 220          position: 'relative'
 221      });
 222      return container;
 223  };
 224  
 225  /**
 226   * Informs the dock that the content of the block has changed.
 227   * This should be called by the blocks JS code if its content has been updated dynamically.
 228   * This method ensure the dock resizes if need be.
 229   *
 230   * @static
 231   * @method notifyBlockChange
 232   * @param {Number} instanceid
 233   * @return void
 234   */
 235  M.core.dock.notifyBlockChange = function(instanceid) {
 236      if (this._dock !== null) {
 237          var dock = M.core.dock.get(),
 238              activeitem = dock.getActiveItem();
 239          if (activeitem && activeitem.get('blockinstanceid') === parseInt(instanceid, 10)) {
 240              dock.resizePanelIfRequired();
 241          }
 242      }
 243  };
 244  
 245  /**
 246   * The Dock.
 247   *
 248   * @namespace M.core.dock
 249   * @class Dock
 250   * @constructor
 251   * @extends Base
 252   * @uses EventTarget
 253   */
 254  DOCK = function() {
 255      DOCK.superclass.constructor.apply(this, arguments);
 256  };
 257  DOCK.prototype = {
 258      /**
 259       * Tab height manager used to ensure tabs are always visible.
 260       * @protected
 261       * @property tabheightmanager
 262       * @type TABHEIGHTMANAGER
 263       */
 264      tabheightmanager: null,
 265      /**
 266       * Will be an eventtype if there is an eventype to prevent.
 267       * @protected
 268       * @property preventevent
 269       * @type String
 270       */
 271      preventevent: null,
 272      /**
 273       * Will be an object if there is a delayed event in effect.
 274       * @protected
 275       * @property delayedevent
 276       * @type {Object}
 277       */
 278      delayedevent: null,
 279      /**
 280       * An array of currently docked items.
 281       * @protected
 282       * @property dockeditems
 283       * @type Array
 284       */
 285      dockeditems: [],
 286      /**
 287       * Set to true once the dock has been drawn.
 288       * @protected
 289       * @property dockdrawn
 290       * @type Boolean
 291       */
 292      dockdrawn: false,
 293      /**
 294       * The number of blocks that are currently docked.
 295       * @protected
 296       * @property count
 297       * @type Number
 298       */
 299      count: 0,
 300      /**
 301       * The total number of blocks that have been docked.
 302       * @protected
 303       * @property totalcount
 304       * @type Number
 305       */
 306      totalcount: 0,
 307      /**
 308       * A hidden node used as a holding area for DOM objects used by blocks that have been docked.
 309       * @protected
 310       * @property holdingareanode
 311       * @type Node
 312       */
 313      holdingareanode: null,
 314      /**
 315       * Called during the initialisation process of the object.
 316       * @method initializer
 317       */
 318      initializer: function() {
 319  
 320          // Publish the events the dock has
 321          /**
 322           * Fired when the dock first starts initialising.
 323           * @event dock:starting
 324           */
 325          this.publish('dock:starting', {prefix: 'dock', broadcast:  2, emitFacade: true, fireOnce: true});
 326          /**
 327           * Fired after the dock is initialised for the first time.
 328           * @event dock:initialised
 329           */
 330          this.publish('dock:initialised', {prefix: 'dock', broadcast:  2, emitFacade: true, fireOnce: true});
 331          /**
 332           * Fired before the dock structure and content is first created.
 333           * @event dock:beforedraw
 334           */
 335          this.publish('dock:beforedraw', {prefix: 'dock', fireOnce: true});
 336          /**
 337           * Fired before the dock is changed from hidden to visible.
 338           * @event dock:beforeshow
 339           */
 340          this.publish('dock:beforeshow', {prefix: 'dock'});
 341          /**
 342           * Fires after the dock has been changed from hidden to visible.
 343           * @event dock:shown
 344           */
 345          this.publish('dock:shown', {prefix: 'dock', broadcast: 2});
 346          /**
 347           * Fired after the dock has been changed from visible to hidden.
 348           * @event dock:hidden
 349           */
 350          this.publish('dock:hidden', {prefix: 'dock', broadcast: 2});
 351          /**
 352           * Fires when an item is added to the dock.
 353           * @event dock:itemadded
 354           */
 355          this.publish('dock:itemadded', {prefix: 'dock'});
 356          /**
 357           * Fires when an item is removed from the dock.
 358           * @event dock:itemremoved
 359           */
 360          this.publish('dock:itemremoved', {prefix: 'dock'});
 361          /**
 362           * Fires when a block is added or removed from the dock.
 363           * This happens after the itemadded and itemremoved events have been called.
 364           * @event dock:itemschanged
 365           */
 366          this.publish('dock:itemschanged', {prefix: 'dock', broadcast: 2});
 367          /**
 368           * Fires once when the docks panel is first initialised.
 369           * @event dock:panelgenerated
 370           */
 371          this.publish('dock:panelgenerated', {prefix: 'dock', fireOnce: true});
 372          /**
 373           * Fires when the dock panel is about to be resized.
 374           * @event dock:panelresizestart
 375           */
 376          this.publish('dock:panelresizestart', {prefix: 'dock'});
 377          /**
 378           * Fires after the dock panel has been resized.
 379           * @event dock:resizepanelcomplete
 380           */
 381          this.publish('dock:resizepanelcomplete', {prefix: 'dock'});
 382  
 383          // Apply theme customisations here before we do any real work.
 384          this._applyThemeCustomisation();
 385          // Inform everyone we are now about to initialise.
 386          this.fire('dock:starting');
 387          this._ensureDockDrawn();
 388          // Inform everyone the dock has been initialised
 389          this.fire('dock:initialised');
 390      },
 391      /**
 392       * Ensures that the dock has been drawn.
 393       * @private
 394       * @method _ensureDockDrawn
 395       * @return {Boolean}
 396       */
 397      _ensureDockDrawn: function() {
 398          if (this.dockdrawn === true) {
 399              return true;
 400          }
 401          var dock = this._initialiseDockNode(),
 402              clickargs = {
 403                  cssselector: '.' + CSS.dockedtitle,
 404                  delay: 0
 405              },
 406              mouseenterargs = {
 407                  cssselector: '.' + CSS.dockedtitle,
 408                  delay: 0.5,
 409                  iscontained: true,
 410                  preventevent: 'click',
 411                  preventdelay: 3
 412              };
 413          if (Y.UA.ie > 0 && Y.UA.ie < 7) {
 414              // Adjust for IE 6 (can't handle fixed pos)
 415              dock.setStyle('height', dock.get('winHeight') + 'px');
 416          }
 417  
 418          this.fire('dock:beforedraw');
 419  
 420          this._initialiseDockControls();
 421  
 422          this.tabheightmanager = new TABHEIGHTMANAGER({dock: this});
 423  
 424          // Attach the required event listeners
 425          // We use delegate here as that way a handful of events are created for the dock
 426          // and all items rather than the same number for the dock AND every item individually
 427          Y.delegate('click', this.handleEvent, this.get('dockNode'), '.' + CSS.dockedtitle, this, clickargs);
 428          Y.delegate('mouseenter', this.handleEvent, this.get('dockNode'), '.' + CSS.dockedtitle, this, mouseenterargs);
 429          this.get('dockNode').on('mouseleave', this.handleEvent, this, {cssselector: '#dock', delay: 0.5, iscontained: false});
 430  
 431          Y.delegate('click', this.handleReturnToBlock, this.get('dockNode'), SELECTOR.panelmoveto, this);
 432          Y.delegate('dock:actionkey', this.handleDockedItemEvent, this.get('dockNode'), '.' + CSS.dockeditem, this);
 433  
 434          BODY.on('click', this.handleEvent, this, {cssselector: 'body', delay: 0});
 435          this.on('dock:itemschanged', this.resizeBlockSpace, this);
 436          this.on('dock:itemschanged', this.checkDockVisibility, this);
 437          this.on('dock:itemschanged', this.resetFirstItem, this);
 438          this.dockdrawn = true;
 439          return true;
 440      },
 441      /**
 442       * Handles an actionkey event on the dock.
 443       * @param {EventFacade} e
 444       * @method handleDockedItemEvent
 445       * @return {Boolean}
 446       */
 447      handleDockedItemEvent: function(e) {
 448          if (e.type !== 'dock:actionkey') {
 449              return false;
 450          }
 451          var target = e.target,
 452              dockeditem = '.' + CSS.dockeditem;
 453          if (!target.test(dockeditem)) {
 454              target = target.ancestor(dockeditem);
 455          }
 456          if (!target) {
 457              return false;
 458          }
 459          e.halt();
 460          this.dockeditems[target.getAttribute('rel')].toggle(e.action);
 461      },
 462      /**
 463       * Call the theme customisation method "customise_dock_for_theme" if it exists.
 464       * @private
 465       * @method _applyThemeCustomisation
 466       */
 467      _applyThemeCustomisation: function() {
 468          // Check if there is a customisation function
 469          if (typeof (customise_dock_for_theme) === 'function') {
 470              // First up pre the legacy object.
 471              M.core_dock = this;
 472              M.core_dock.cfg = {
 473                  buffer: null,
 474                  orientation: null,
 475                  position: null,
 476                  spacebeforefirstitem: null,
 477                  removeallicon: null
 478              };
 479              M.core_dock.css = {
 480                  dock: null,
 481                  dockspacer: null,
 482                  controls: null,
 483                  body: null,
 484                  buttonscontainer: null,
 485                  dockeditem: null,
 486                  dockeditemcontainer: null,
 487                  dockedtitle: null,
 488                  activeitem: null
 489              };
 490              try {
 491                  // Run the customisation function
 492                  window.customise_dock_for_theme(this);
 493              } catch (exception) {
 494                  // Do nothing at the moment.
 495              }
 496              // Now to work out what they did.
 497              var key, value,
 498                  warned = false,
 499                  cfgmap = {
 500                      buffer: 'bufferPanel',
 501                      orientation: 'orientation',
 502                      position: 'position',
 503                      spacebeforefirstitem: 'bufferBeforeFirstItem',
 504                      removeallicon: 'undockAllIconUrl'
 505                  };
 506              // Check for and apply any legacy configuration.
 507              for (key in M.core_dock.cfg) {
 508                  if (Y.Lang.isString(key) && cfgmap[key]) {
 509                      value = M.core_dock.cfg[key];
 510                      if (value === null) {
 511                          continue;
 512                      }
 513                      if (!warned) {
 514                          warned = true;
 515                      }
 516                      // Damn, the've set something.
 517                      this.set(cfgmap[key], value);
 518                  }
 519              }
 520              // Check for and apply any legacy CSS changes..
 521              for (key in M.core_dock.css) {
 522                  if (Y.Lang.isString(key)) {
 523                      value = M.core_dock.css[key];
 524                      if (value === null) {
 525                          continue;
 526                      }
 527                      if (!warned) {
 528                          warned = true;
 529                      }
 530                      // Damn, they've set something.
 531                      CSS[key] = value;
 532                  }
 533              }
 534          }
 535      },
 536      /**
 537       * Initialises the dock node, creating it and its content if required.
 538       *
 539       * @private
 540       * @method _initialiseDockNode
 541       * @return {Node} The dockNode
 542       */
 543      _initialiseDockNode: function() {
 544          var dock = this.get('dockNode'),
 545              positionorientationclass = CSS.dock + '_' + this.get('position') + '_' + this.get('orientation'),
 546              holdingarea = Y.Node.create('<div></div>').setStyles({display: 'none'}),
 547              buttons = this.get('buttonsNode'),
 548              container = this.get('itemContainerNode');
 549  
 550          if (!dock) {
 551              dock = Y.one('#' + CSS.dock);
 552          }
 553          if (!dock) {
 554              dock = Y.Node.create('<div id="' + CSS.dock + '"></div>');
 555              BODY.append(dock);
 556          }
 557          dock.setAttribute('role', 'menubar').addClass(positionorientationclass);
 558          if (Y.all(SELECTOR.dockonload).size() === 0) {
 559              // Nothing on the dock... hide it using CSS
 560              dock.addClass('nothingdocked');
 561          } else {
 562              positionorientationclass = CSS.body + '_' + this.get('position') + '_' + this.get('orientation');
 563              BODY.addClass(CSS.body).addClass();
 564          }
 565  
 566          if (!buttons) {
 567              buttons = dock.one('.' + CSS.buttonscontainer);
 568          }
 569          if (!buttons) {
 570              buttons = Y.Node.create('<div class="' + CSS.buttonscontainer + '"></div>');
 571              dock.append(buttons);
 572          }
 573  
 574          if (!container) {
 575              container = dock.one('.' + CSS.dockeditemcontainer);
 576          }
 577          if (!container) {
 578              container = Y.Node.create('<div class="' + CSS.dockeditemcontainer + '"></div>');
 579              buttons.append(container);
 580          }
 581  
 582          BODY.append(holdingarea);
 583          this.holdingareanode = holdingarea;
 584  
 585          this.set('dockNode', dock);
 586          this.set('buttonsNode', buttons);
 587          this.set('itemContainerNode', container);
 588  
 589          return dock;
 590      },
 591      /**
 592       * Initialises the dock controls.
 593       *
 594       * @private
 595       * @method _initialiseDockControls
 596       */
 597      _initialiseDockControls: function() {
 598          // Add a removeall button
 599          // Must set the image src seperatly of we get an error with XML strict headers
 600  
 601          var removeall = Y.Node.create('<img alt="' + M.util.get_string('undockall', 'block') + '" tabindex="0" />');
 602          removeall.setAttribute('src', this.get('undockAllIconUrl'));
 603          removeall.on('removeall|click', this.removeAll, this);
 604          removeall.on('dock:actionkey', this.removeAll, this, {actions: {enter: true}});
 605          this.get('buttonsNode').append(Y.Node.create('<div class="' + CSS.controls + '"></div>').append(removeall));
 606      },
 607      /**
 608       * Returns the dock panel. Initialising it if it hasn't already been initialised.
 609       * @method getPanel
 610       * @return {DOCKPANEL}
 611       */
 612      getPanel: function() {
 613          var panel = this.get('panel');
 614          if (!panel) {
 615              panel = new DOCKPANEL({dock: this});
 616              panel.on('panel:visiblechange', this.resize, this);
 617              Y.on('windowresize', this.resize, this);
 618              // Initialise the dockpanel .. should only happen once
 619              this.set('panel', panel);
 620              this.fire('dock:panelgenerated');
 621          }
 622          return panel;
 623      },
 624      /**
 625       * Resizes the dock panel if required.
 626       * @method resizePanelIfRequired
 627       */
 628      resizePanelIfRequired: function() {
 629          this.resize();
 630          var panel = this.get('panel');
 631          if (panel) {
 632              panel.correctWidth();
 633          }
 634      },
 635      /**
 636       * Handles a dock event sending it to the right place.
 637       *
 638       * @method handleEvent
 639       * @param {EventFacade} e
 640       * @param {Object} options
 641       * @return {Boolean}
 642       */
 643      handleEvent: function(e, options) {
 644          var item = this.getActiveItem(),
 645              target,
 646              targetid,
 647              regex = /^dock_item_(\d+)_title$/,
 648              self = this;
 649          if (options.cssselector === 'body') {
 650              if (!this.get('dockNode').contains(e.target)) {
 651                  if (item) {
 652                      item.hide();
 653                  }
 654              }
 655          } else {
 656              if (e.target.test(options.cssselector)) {
 657                  target = e.target;
 658              } else {
 659                  target = e.target.ancestor(options.cssselector);
 660              }
 661              if (!target) {
 662                  return true;
 663              }
 664              if (this.preventevent !== null && e.type === this.preventevent) {
 665                  return true;
 666              }
 667              if (options.preventevent) {
 668                  this.preventevent = options.preventevent;
 669                  if (options.preventdelay) {
 670                      setTimeout(function() {
 671                          self.preventevent = null;
 672                      }, options.preventdelay * 1000);
 673                  }
 674              }
 675              if (this.delayedevent && this.delayedevent.timeout) {
 676                  clearTimeout(this.delayedevent.timeout);
 677                  this.delayedevent.event.detach();
 678                  this.delayedevent = null;
 679              }
 680              if (options.delay > 0) {
 681                  return this.delayEvent(e, options, target);
 682              }
 683              targetid = target.get('id');
 684              if (targetid.match(regex)) {
 685                  item = this.dockeditems[targetid.replace(regex, '$1')];
 686                  if (item.active) {
 687                      item.hide();
 688                  } else {
 689                      item.show();
 690                  }
 691              } else if (item) {
 692                  item.hide();
 693              }
 694          }
 695          return true;
 696      },
 697      /**
 698       * Delays an event.
 699       *
 700       * @method delayEvent
 701       * @param {EventFacade} event
 702       * @param {Object} options
 703       * @param {Node} target
 704       * @return {Boolean}
 705       */
 706      delayEvent: function(event, options, target) {
 707          var self = this;
 708          self.delayedevent = (function() {
 709              return {
 710                  target: target,
 711                  event: BODY.on('mousemove', function(e) {
 712                      self.delayedevent.target = e.target;
 713                  }),
 714                  timeout: null
 715              };
 716          })(self);
 717          self.delayedevent.timeout = setTimeout(function() {
 718              self.delayedevent.timeout = null;
 719              self.delayedevent.event.detach();
 720              if (options.iscontained === self.get('dockNode').contains(self.delayedevent.target)) {
 721                  self.handleEvent(event, {cssselector: options.cssselector, delay: 0, iscontained: options.iscontained});
 722              }
 723          }, options.delay * 1000);
 724          return true;
 725      },
 726      /**
 727       * Resizes block spaces.
 728       * @method resizeBlockSpace
 729       */
 730      resizeBlockSpace: function() {
 731          if (Y.all(SELECTOR.dockonload).size() > 0) {
 732              // Do not resize during initial load
 733              return;
 734          }
 735  
 736          var populatedRegionCount = 0,
 737              populatedBlockRegions = [],
 738              unpopulatedBlockRegions = [],
 739              isMoving = false,
 740              populatedLegacyRegions = [],
 741              containsLegacyRegions = false,
 742              classesToAdd = [],
 743              classesToRemove = [];
 744  
 745          // First look for understood regions.
 746          Y.all(SELECTOR.blockregion).each(function(region) {
 747              var regionname = region.getData('blockregion');
 748              if (region.all('.block').size() > 0) {
 749                  populatedBlockRegions.push(regionname);
 750                  populatedRegionCount++;
 751              } else if (region.all('.block_dock_placeholder').size() > 0) {
 752                  unpopulatedBlockRegions.push(regionname);
 753              }
 754          });
 755  
 756          // Next check for legacy regions.
 757          Y.all('.block-region').each(function(region) {
 758              if (region.test(SELECTOR.blockregion)) {
 759                  // This is a new region, we've already processed it.
 760                  return;
 761              }
 762  
 763              // Sigh - there are legacy regions.
 764              containsLegacyRegions = true;
 765  
 766              var regionname = region.get('id').replace(/^region\-/, 'side-'),
 767                  hasblocks = (region.all('.block').size() > 0);
 768  
 769              if (hasblocks) {
 770                  populatedLegacyRegions.push(regionname);
 771                  populatedRegionCount++;
 772              } else {
 773                  // This legacy region has no blocks so cannot have the -only body tag.
 774                  classesToRemove.push(
 775                          regionname + '-only'
 776                      );
 777              }
 778          });
 779  
 780          if (BODY.hasClass('blocks-moving')) {
 781              // When we're moving blocks, we do not want to collapse.
 782              isMoving = true;
 783          }
 784  
 785          Y.each(unpopulatedBlockRegions, function(regionname) {
 786              classesToAdd.push(
 787                      // This block region is empty.
 788                      'empty-region-' + regionname,
 789  
 790                      // Which has the same effect as being docked.
 791                      'docked-region-' + regionname
 792                  );
 793              classesToRemove.push(
 794                      // It is no-longer used.
 795                      'used-region-' + regionname,
 796  
 797                      // It cannot be the only region on screen if it is empty.
 798                      regionname + '-only'
 799                  );
 800          }, this);
 801  
 802          Y.each(populatedBlockRegions, function(regionname) {
 803              classesToAdd.push(
 804                      // This block region is in use.
 805                      'used-region-' + regionname
 806                  );
 807              classesToRemove.push(
 808                      // It is not empty.
 809                      'empty-region-' + regionname,
 810  
 811                      // Is it not docked.
 812                      'docked-region-' + regionname
 813                  );
 814  
 815              if (populatedRegionCount === 1 && isMoving === false) {
 816                  // There was only one populated region, and we are not moving blocks.
 817                  classesToAdd.push(regionname + '-only');
 818              } else {
 819                  // There were multiple block regions visible - remove any 'only' classes.
 820                  classesToRemove.push(regionname + '-only');
 821              }
 822          }, this);
 823  
 824          if (containsLegacyRegions) {
 825              // Handle the classing for legacy blocks. These have slightly different class names for the body.
 826              if (isMoving || populatedRegionCount !== 1) {
 827                  Y.each(populatedLegacyRegions, function(regionname) {
 828                      classesToRemove.push(regionname + '-only');
 829                  });
 830              } else {
 831                  Y.each(populatedLegacyRegions, function(regionname) {
 832                      classesToAdd.push(regionname + '-only');
 833                  });
 834              }
 835          }
 836  
 837          if (!BODY.hasClass('has-region-content')) {
 838              // This page does not have a content region, therefore content-only is implied when all block regions are docked.
 839              if (populatedRegionCount === 0 && isMoving === false) {
 840                  // If all blocks are docked, ensure that the content-only class is added anyway.
 841                  classesToAdd.push('content-only');
 842              } else {
 843                  // Otherwise remove it.
 844                  classesToRemove.push('content-only');
 845              }
 846          }
 847  
 848          // Modify the body clases.
 849          Y.each(classesToRemove, function(className) {
 850              BODY.removeClass(className);
 851          });
 852          Y.each(classesToAdd, function(className) {
 853              BODY.addClass(className);
 854          });
 855      },
 856      /**
 857       * Adds an item to the dock.
 858       * @method add
 859       * @param {DOCKEDITEM} item
 860       */
 861      add: function(item) {
 862          // Set the dockitem id to the total count and then increment it.
 863          item.set('id', this.totalcount);
 864          this.count++;
 865          this.totalcount++;
 866          this.dockeditems[item.get('id')] = item;
 867          this.dockeditems[item.get('id')].draw();
 868          this.fire('dock:itemadded', item);
 869          this.fire('dock:itemschanged', item);
 870      },
 871      /**
 872       * Appends an item to the dock (putting it in the item container.
 873       * @method append
 874       * @param {Node} docknode
 875       */
 876      append: function(docknode) {
 877          this.get('itemContainerNode').append(docknode);
 878      },
 879      /**
 880       * Handles events that require a docked block to be returned to the page./
 881       * @method handleReturnToBlock
 882       * @param {EventFacade} e
 883       */
 884      handleReturnToBlock: function(e) {
 885          e.halt();
 886          this.remove(this.getActiveItem().get('id'));
 887      },
 888      /**
 889       * Removes a docked item from the dock.
 890       * @method remove
 891       * @param {Number} id The docked item id.
 892       * @return {Boolean}
 893       */
 894      remove: function(id) {
 895          if (!this.dockeditems[id]) {
 896              return false;
 897          }
 898          this.dockeditems[id].remove();
 899          delete this.dockeditems[id];
 900          this.count--;
 901          this.fire('dock:itemremoved', id);
 902          this.fire('dock:itemschanged', id);
 903          return true;
 904      },
 905      /**
 906       * Ensures the the first item in the dock has the correct class.
 907       * @method resetFirstItem
 908       */
 909      resetFirstItem: function() {
 910          this.get('dockNode').all('.' + CSS.dockeditem + '.firstdockitem').removeClass('firstdockitem');
 911          if (this.get('dockNode').one('.' + CSS.dockeditem)) {
 912              this.get('dockNode').one('.' + CSS.dockeditem).addClass('firstdockitem');
 913          }
 914      },
 915      /**
 916       * Removes all docked blocks returning them to the page.
 917       * @method removeAll
 918       * @return {Boolean}
 919       */
 920      removeAll: function() {
 921          var i;
 922          for (i in this.dockeditems) {
 923              if (Y.Lang.isNumber(i) || Y.Lang.isString(i)) {
 924                  this.remove(i);
 925              }
 926          }
 927          return true;
 928      },
 929      /**
 930       * Hides the active item.
 931       * @method hideActive
 932       */
 933      hideActive: function() {
 934          var item = this.getActiveItem();
 935          if (item) {
 936              item.hide();
 937          }
 938      },
 939      /**
 940       * Checks wether the dock should be shown or hidden
 941       * @method checkDockVisibility
 942       */
 943      checkDockVisibility: function() {
 944          var bodyclass = CSS.body + '_' + this.get('position') + '_' + this.get('orientation');
 945          if (!this.count) {
 946              this.get('dockNode').addClass('nothingdocked');
 947              BODY.removeClass(CSS.body).removeClass();
 948              this.fire('dock:hidden');
 949          } else {
 950              this.fire('dock:beforeshow');
 951              this.get('dockNode').removeClass('nothingdocked');
 952              BODY.addClass(CSS.body).addClass(bodyclass);
 953              this.fire('dock:shown');
 954          }
 955      },
 956      /**
 957       * This function checks the size and position of the panel and moves/resizes if
 958       * required to keep it within the bounds of the window.
 959       * @method resize
 960       * @return {Boolean}
 961       */
 962      resize: function() {
 963          var panel = this.getPanel(),
 964              item = this.getActiveItem(),
 965              buffer,
 966              screenh,
 967              docky,
 968              titletop,
 969              containery,
 970              containerheight,
 971              scrolltop,
 972              panelheight,
 973              dockx,
 974              titleleft;
 975          if (!panel.get('visible') || !item) {
 976              return true;
 977          }
 978  
 979          this.fire('dock:panelresizestart');
 980          if (this.get('orientation') === 'vertical') {
 981              buffer = this.get('bufferPanel');
 982              screenh = parseInt(BODY.get('winHeight'), 10) - (buffer * 2);
 983              docky = this.get('dockNode').getY();
 984              titletop = item.get('dockTitleNode').getY() - docky - buffer;
 985              containery = this.get('itemContainerNode').getY();
 986              containerheight = containery - docky + this.get('buttonsNode').get('offsetHeight');
 987              scrolltop = panel.get('bodyNode').get('scrollTop');
 988              panel.get('bodyNode').setStyle('height', 'auto');
 989              panel.get('node').removeClass('oversized_content');
 990              panelheight = panel.get('node').get('offsetHeight');
 991  
 992              if (Y.UA.ie > 0 && Y.UA.ie < 7) {
 993                  panel.setTop(item.get('dockTitleNode').getY());
 994              } else if (panelheight > screenh) {
 995                  panel.setTop(buffer - containerheight);
 996                  panel.get('bodyNode').setStyle('height', (screenh - panel.get('headerNode').get('offsetHeight')) + 'px');
 997                  panel.get('node').addClass('oversized_content');
 998              } else if (panelheight > (screenh - (titletop - buffer))) {
 999                  panel.setTop(titletop - containerheight - (panelheight - (screenh - titletop)) + buffer);
1000              } else {
1001                  panel.setTop(titletop - containerheight + buffer);
1002              }
1003  
1004              if (scrolltop) {
1005                  panel.get('bodyNode').set('scrollTop', scrolltop);
1006              }
1007          }
1008  
1009          if (this.get('position') === 'right') {
1010              panel.get('node').setStyle('left', '-' + panel.get('node').get('offsetWidth') + 'px');
1011  
1012          } else if (this.get('position') === 'top') {
1013              dockx = this.get('dockNode').getX();
1014              titleleft = item.get('dockTitleNode').getX() - dockx;
1015              panel.get('node').setStyle('left', titleleft + 'px');
1016          }
1017  
1018          this.fire('dock:resizepanelcomplete');
1019          return true;
1020      },
1021      /**
1022       * Returns the currently active dock item or false
1023       * @method getActiveItem
1024       * @return {DOCKEDITEM}
1025       */
1026      getActiveItem: function() {
1027          var i;
1028          for (i in this.dockeditems) {
1029              if (this.dockeditems[i].active) {
1030                  return this.dockeditems[i];
1031              }
1032          }
1033          return false;
1034      },
1035      /**
1036       * Adds an item to the holding area.
1037       * @method addToHoldingArea
1038       * @param {Node} node
1039       */
1040      addToHoldingArea: function(node) {
1041          this.holdingareanode.append(node);
1042      }
1043  };
1044  
1045  Y.extend(DOCK, Y.Base, DOCK.prototype, {
1046      NAME: 'moodle-core-dock',
1047      ATTRS: {
1048          /**
1049           * The dock itself. #dock.
1050           * @attribute dockNode
1051           * @type Node
1052           * @writeOnce
1053           */
1054          dockNode: {
1055              writeOnce: true
1056          },
1057          /**
1058           * The docks panel.
1059           * @attribute panel
1060           * @type DOCKPANEL
1061           * @writeOnce
1062           */
1063          panel: {
1064              writeOnce: true
1065          },
1066          /**
1067           * A container within the dock used for buttons.
1068           * @attribute buttonsNode
1069           * @type Node
1070           * @writeOnce
1071           */
1072          buttonsNode: {
1073              writeOnce: true
1074          },
1075          /**
1076           * A container within the dock used for docked blocks.
1077           * @attribute itemContainerNode
1078           * @type Node
1079           * @writeOnce
1080           */
1081          itemContainerNode: {
1082              writeOnce: true
1083          },
1084  
1085          /**
1086           * Buffer used when containing a panel.
1087           * @attribute bufferPanel
1088           * @type Number
1089           * @default 10
1090           */
1091          bufferPanel: {
1092              value: 10,
1093              validator: Y.Lang.isNumber
1094          },
1095  
1096          /**
1097           * Position of the dock.
1098           * @attribute position
1099           * @type String
1100           * @default left
1101           */
1102          position: {
1103              value: 'left',
1104              validator: Y.Lang.isString
1105          },
1106  
1107          /**
1108           * vertical || horizontal determines if we change the title
1109           * @attribute orientation
1110           * @type String
1111           * @default vertical
1112           */
1113          orientation: {
1114              value: 'vertical',
1115              validator: Y.Lang.isString,
1116              setter: function(value) {
1117                  if (value.match(/^vertical$/i)) {
1118                      return 'vertical';
1119                  }
1120                  return 'horizontal';
1121              }
1122          },
1123  
1124          /**
1125           * Space between the top of the dock and the first item.
1126           * @attribute bufferBeforeFirstItem
1127           * @type Number
1128           * @default 10
1129           */
1130          bufferBeforeFirstItem: {
1131              value: 10,
1132              validator: Y.Lang.isNumber
1133          },
1134  
1135          /**
1136           * Icon URL for the icon to undock all blocks
1137           * @attribute undockAllIconUrl
1138           * @type String
1139           * @default t/dock_to_block
1140           */
1141          undockAllIconUrl: {
1142              value: M.util.image_url((window.right_to_left()) ? 't/dock_to_block_rtl' : 't/dock_to_block', 'moodle'),
1143              validator: Y.Lang.isString
1144          }
1145      }
1146  });
1147  Y.augment(DOCK, Y.EventTarget);
1148  /* global DOCKPANEL, LOGNS */
1149  
1150  /**
1151   * Dock JS.
1152   *
1153   * This file contains the panel class used by the dock to display the content of docked blocks.
1154   *
1155   * @module moodle-core-dock
1156   */
1157  
1158  /**
1159   * Panel.
1160   *
1161   * @namespace M.core.dock
1162   * @class Panel
1163   * @constructor
1164   * @extends Base
1165   * @uses EventTarget
1166   */
1167  DOCKPANEL = function() {
1168      DOCKPANEL.superclass.constructor.apply(this, arguments);
1169  };
1170  DOCKPANEL.prototype = {
1171      /**
1172       * True once the panel has been created.
1173       * @property created
1174       * @protected
1175       * @type {Boolean}
1176       */
1177      created: false,
1178      /**
1179       * Called during the initialisation process of the object.
1180       * @method initializer
1181       */
1182      initializer: function() {
1183          /**
1184           * Fired before the panel is shown.
1185           * @event dockpane::beforeshow
1186           */
1187          this.publish('dockpanel:beforeshow', {prefix: 'dockpanel'});
1188          /**
1189           * Fired after the panel is shown.
1190           * @event dockpanel:shown
1191           */
1192          this.publish('dockpanel:shown', {prefix: 'dockpanel'});
1193          /**
1194           * Fired before the panel is hidden.
1195           * @event dockpane::beforehide
1196           */
1197          this.publish('dockpanel:beforehide', {prefix: 'dockpanel'});
1198          /**
1199           * Fired after the panel is hidden.
1200           * @event dockpanel:hidden
1201           */
1202          this.publish('dockpanel:hidden', {prefix: 'dockpanel'});
1203          /**
1204           * Fired when ever the dock panel is either hidden or shown.
1205           * Always fired after the shown or hidden events.
1206           * @event dockpanel:visiblechange
1207           */
1208          this.publish('dockpanel:visiblechange', {prefix: 'dockpanel'});
1209      },
1210      /**
1211       * Creates the Panel if it has not already been created.
1212       * @method create
1213       * @return {Boolean}
1214       */
1215      create: function() {
1216          if (this.created) {
1217              return true;
1218          }
1219          this.created = true;
1220          var dock = this.get('dock'),
1221              node = dock.get('dockNode');
1222          this.set('node', Y.Node.create('<div id="dockeditempanel" class="dockitempanel_hidden"></div>'));
1223          this.set('contentNode', Y.Node.create('<div class="dockeditempanel_content"></div>'));
1224          this.set('headerNode', Y.Node.create('<div class="dockeditempanel_hd"></div>'));
1225          this.set('bodyNode', Y.Node.create('<div class="dockeditempanel_bd"></div>'));
1226          node.append(
1227              this.get('node').append(this.get('contentNode').append(this.get('headerNode')).append(this.get('bodyNode')))
1228          );
1229      },
1230      /**
1231       * Displays the panel.
1232       * @method show
1233       */
1234      show: function() {
1235          this.create();
1236          this.fire('dockpanel:beforeshow');
1237          this.set('visible', true);
1238          this.get('node').removeClass('dockitempanel_hidden');
1239          this.fire('dockpanel:shown');
1240          this.fire('dockpanel:visiblechange');
1241      },
1242      /**
1243       * Hides the panel
1244       * @method hide
1245       */
1246      hide: function() {
1247          this.fire('dockpanel:beforehide');
1248          this.set('visible', false);
1249          this.get('node').addClass('dockitempanel_hidden');
1250          this.fire('dockpanel:hidden');
1251          this.fire('dockpanel:visiblechange');
1252      },
1253      /**
1254       * Sets the panel header.
1255       * @method setHeader
1256       * @param {Node|String} content
1257       */
1258      setHeader: function(content) {
1259          this.create();
1260          var header = this.get('headerNode'),
1261              i;
1262          header.setContent(content);
1263          if (arguments.length > 1) {
1264              for (i = 1; i < arguments.length; i++) {
1265                  if (Y.Lang.isNumber(i) || Y.Lang.isString(i)) {
1266                      header.append(arguments[i]);
1267                  }
1268              }
1269          }
1270      },
1271      /**
1272       * Sets the panel body.
1273       * @method setBody
1274       * @param {Node|String} content
1275       */
1276      setBody: function(content) {
1277          this.create();
1278          this.get('bodyNode').setContent(content);
1279      },
1280      /**
1281       * Sets the new top mark of the panel.
1282       *
1283       * @method setTop
1284       * @param {Number} newtop
1285       */
1286      setTop: function(newtop) {
1287          if (Y.UA.ie > 0 && Y.UA.ie < 7) {
1288              this.get('node').setY(newtop);
1289          } else {
1290              this.get('node').setStyle('top', newtop.toString() + 'px');
1291          }
1292      },
1293      /**
1294       * Corrects the width of the panel.
1295       * @method correctWidth
1296       */
1297      correctWidth: function() {
1298          var bodyNode = this.get('bodyNode'),
1299              // Width of content.
1300              width = bodyNode.get('clientWidth'),
1301              // Scrollable width of content.
1302              scroll = bodyNode.get('scrollWidth'),
1303              // Width of content container with overflow.
1304              offsetWidth = bodyNode.get('offsetWidth'),
1305              // The new width - defaults to the current width.
1306              newWidth = width,
1307              // The max width (80% of screen).
1308              maxWidth = Math.round(bodyNode.get('winWidth') * 0.8);
1309  
1310          // If the scrollable width is more than the visible width
1311          if (scroll > width) {
1312              //   Content width
1313              // + the difference
1314              // + any rendering difference (borders, padding)
1315              // + 10px to make it look nice.
1316              newWidth = width + (scroll - width) + ((offsetWidth - width) * 2) + 10;
1317          }
1318  
1319          // Make sure its not more then the maxwidth
1320          if (newWidth > maxWidth) {
1321              newWidth = maxWidth;
1322          }
1323  
1324          // Set the new width if its more than the old width.
1325          if (newWidth > offsetWidth) {
1326              this.get('node').setStyle('width', newWidth + 'px');
1327          }
1328      }
1329  };
1330  Y.extend(DOCKPANEL, Y.Base, DOCKPANEL.prototype, {
1331      NAME: 'moodle-core-dock-panel',
1332      ATTRS: {
1333          /**
1334           * The dock itself.
1335           * @attribute dock
1336           * @type DOCK
1337           * @writeonce
1338           */
1339          dock: {
1340              writeOnce: 'initOnly'
1341          },
1342          /**
1343           * The node that contains the whole panel.
1344           * @attribute node
1345           * @type Node
1346           */
1347          node: {
1348              value: null
1349          },
1350          /**
1351           * The node that contains the header, body and footer.
1352           * @attribute contentNode
1353           * @type Node
1354           */
1355          contentNode: {
1356              value: null
1357          },
1358          /**
1359           * The node that contains the header
1360           * @attribute headerNode
1361           * @type Node
1362           */
1363          headerNode: {
1364              value: null
1365          },
1366          /**
1367           * The node that contains the body
1368           * @attribute bodyNode
1369           * @type Node
1370           */
1371          bodyNode: {
1372              value: null
1373          },
1374          /**
1375           * True if the panel is currently visible.
1376           * @attribute visible
1377           * @type Boolean
1378           */
1379          visible: {
1380              value: false
1381          }
1382      }
1383  });
1384  Y.augment(DOCKPANEL, Y.EventTarget);
1385  /* global TABHEIGHTMANAGER, LOGNS */
1386  
1387  /**
1388   * Dock JS.
1389   *
1390   * This file contains the tab height manager.
1391   * The tab height manager is responsible for ensure all tabs are visible all the time.
1392   *
1393   * @module moodle-core-dock
1394   */
1395  
1396  /**
1397   * Tab height manager.
1398   *
1399   * @namespace M.core.dock
1400   * @class TabHeightManager
1401   * @constructor
1402   * @extends Base
1403   */
1404  TABHEIGHTMANAGER = function() {
1405      TABHEIGHTMANAGER.superclass.constructor.apply(this, arguments);
1406  };
1407  TABHEIGHTMANAGER.prototype = {
1408      /**
1409       * Initialises the dock sizer which then attaches itself to the required
1410       * events in order to monitor the dock
1411       * @method initializer
1412       */
1413      initializer: function() {
1414          var dock = this.get('dock');
1415          dock.on('dock:itemschanged', this.checkSizing, this);
1416          Y.on('windowresize', this.checkSizing, this);
1417      },
1418      /**
1419       * Check if the size dock items needs to be adjusted
1420       * @method checkSizing
1421       */
1422      checkSizing: function() {
1423          var dock = this.get('dock'),
1424              node = dock.get('dockNode'),
1425              items = dock.dockeditems,
1426              containermargin = parseInt(node.one('.dockeditem_container').getStyle('marginTop').replace('/[^0-9]+$/', ''), 10),
1427              dockheight = node.get('offsetHeight') - containermargin,
1428              controlheight = node.one('.controls').get('offsetHeight'),
1429              buffer = (dock.get('bufferPanel') * 3),
1430              possibleheight = dockheight - controlheight - buffer - (items.length * 2),
1431              totalheight = 0,
1432              id, dockedtitle;
1433          if (items.length > 0) {
1434              for (id in items) {
1435                  if (Y.Lang.isNumber(id) || Y.Lang.isString(id)) {
1436                      dockedtitle = Y.one(items[id].get('title')).ancestor('.' + CSS.dockedtitle);
1437                      if (dockedtitle) {
1438                          if (this.get('enabled')) {
1439                              dockedtitle.setStyle('height', 'auto');
1440                          }
1441                          totalheight += dockedtitle.get('offsetHeight') || 0;
1442                      }
1443                  }
1444              }
1445              if (totalheight > possibleheight) {
1446                  this.enable(possibleheight);
1447              }
1448          }
1449      },
1450      /**
1451       * Enables the dock sizer and resizes where required.
1452       * @method enable
1453       * @param {Number} possibleheight
1454       */
1455      enable: function(possibleheight) {
1456          var dock = this.get('dock'),
1457              items = dock.dockeditems,
1458              count = dock.count,
1459              runningcount = 0,
1460              usedheight = 0,
1461              id, itemtitle, itemheight, offsetheight;
1462          this.set('enabled', true);
1463          for (id in items) {
1464              if (Y.Lang.isNumber(id) || Y.Lang.isString(id)) {
1465                  itemtitle = Y.one(items[id].get('title')).ancestor('.' + CSS.dockedtitle);
1466                  if (!itemtitle) {
1467                      continue;
1468                  }
1469                  itemheight = Math.floor((possibleheight - usedheight) / (count - runningcount));
1470                  offsetheight = itemtitle.get('offsetHeight');
1471                  itemtitle.setStyle('overflow', 'hidden');
1472                  if (offsetheight > itemheight) {
1473                      itemtitle.setStyle('height', itemheight + 'px');
1474                      usedheight += itemheight;
1475                  } else {
1476                      usedheight += offsetheight;
1477                  }
1478                  runningcount++;
1479              }
1480          }
1481      }
1482  };
1483  Y.extend(TABHEIGHTMANAGER, Y.Base, TABHEIGHTMANAGER.prototype, {
1484      NAME: 'moodle-core-tabheightmanager',
1485      ATTRS: {
1486          /**
1487           * The dock.
1488           * @attribute dock
1489           * @type DOCK
1490           * @writeOnce
1491           */
1492          dock: {
1493              writeOnce: 'initOnly'
1494          },
1495          /**
1496           * True if the item_sizer is being used, false otherwise.
1497           * @attribute enabled
1498           * @type Bool
1499           */
1500          enabled: {
1501              value: false
1502          }
1503      }
1504  });
1505  /**
1506   * Dock JS.
1507   *
1508   * This file contains the action key event definition that is used for accessibility handling within the Dock.
1509   *
1510   * @module moodle-core-dock
1511   */
1512  
1513  /**
1514   * A 'dock:actionkey' Event.
1515   * The event consists of the left arrow, right arrow, enter and space keys.
1516   * More keys can be mapped to action meanings.
1517   * actions: collapse , expand, toggle, enter.
1518   *
1519   * This event is subscribed to by dockitems.
1520   * The on() method to subscribe allows specifying the desired trigger actions as JSON.
1521   *
1522   * This event can also be delegated if needed.
1523   *
1524   * @namespace M.core.dock
1525   * @class ActionKey
1526   */
1527  Y.Event.define("dock:actionkey", {
1528      // Webkit and IE repeat keydown when you hold down arrow keys.
1529      // Opera links keypress to page scroll; others keydown.
1530      // Firefox prevents page scroll via preventDefault() on either
1531      // keydown or keypress.
1532      _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
1533  
1534      /**
1535       * The keys to trigger on.
1536       * @property _keys
1537       */
1538      _keys: {
1539          // arrows
1540          '37': 'collapse',
1541          '39': 'expand',
1542          // (@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings)
1543          '32': 'toggle',
1544          '13': 'enter'
1545      },
1546  
1547      /**
1548       * Handles key events
1549       * @method _keyHandler
1550       * @param {EventFacade} e
1551       * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
1552       * @param {Object} args
1553       */
1554      _keyHandler: function(e, notifier, args) {
1555          var actObj;
1556          if (!args.actions) {
1557              actObj = {collapse: true, expand: true, toggle: true, enter: true};
1558          } else {
1559              actObj = args.actions;
1560          }
1561          if (this._keys[e.keyCode] && actObj[this._keys[e.keyCode]]) {
1562              e.action = this._keys[e.keyCode];
1563              notifier.fire(e);
1564          }
1565      },
1566  
1567      /**
1568       * Subscribes to events.
1569       * @method on
1570       * @param {Node} node The node this subscription was applied to.
1571       * @param {Subscription} sub The object tracking this subscription.
1572       * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
1573       */
1574      on: function(node, sub, notifier) {
1575          // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
1576          if (sub.args === null) {
1577              // no actions given
1578              sub._detacher = node.on(this._event, this._keyHandler, this, notifier, {actions: false});
1579          } else {
1580              sub._detacher = node.on(this._event, this._keyHandler, this, notifier, sub.args[0]);
1581          }
1582      },
1583  
1584      /**
1585       * Detaches an event listener
1586       * @method detach
1587       * @param {Node} node The node this subscription was applied to.
1588       * @param {Subscription} sub The object tracking this subscription.
1589       * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
1590       */
1591      detach: function(node, sub) {
1592          // detach our _detacher handle of the subscription made in on()
1593          sub._detacher.detach();
1594      },
1595  
1596      /**
1597       * Creates a delegated event listener.
1598       * @method delegate
1599       * @param {Node} node The node this subscription was applied to.
1600       * @param {Subscription} sub The object tracking this subscription.
1601       * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
1602       * @param {String|function} filter Selector string or function that accpets an event object and returns null.
1603       */
1604      delegate: function(node, sub, notifier, filter) {
1605          // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
1606          if (sub.args === null) {
1607              // no actions given
1608              sub._delegateDetacher = node.delegate(this._event, this._keyHandler, filter, this, notifier, {actions: false});
1609          } else {
1610              sub._delegateDetacher = node.delegate(this._event, this._keyHandler, filter, this, notifier, sub.args[0]);
1611          }
1612      },
1613  
1614      /**
1615       * Detaches a delegated event listener.
1616       * @method detachDelegate
1617       * @param {Node} node The node this subscription was applied to.
1618       * @param {Subscription} sub The object tracking this subscription.
1619       * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
1620       * @param {String|function} filter Selector string or function that accpets an event object and returns null.
1621       */
1622      detachDelegate: function(node, sub) {
1623          sub._delegateDetacher.detach();
1624      }
1625  });
1626  /* global BLOCK, LOGNS, DOCKEDITEM */
1627  
1628  /**
1629   * Dock JS.
1630   *
1631   * This file contains the block class used to manage blocks (both docked and not) for the dock.
1632   *
1633   * @module moodle-core-dock
1634   */
1635  
1636  /**
1637   * Block.
1638   *
1639   * @namespace M.core.dock
1640   * @class Block
1641   * @constructor
1642   * @extends Base
1643   */
1644  BLOCK = function() {
1645      BLOCK.superclass.constructor.apply(this, arguments);
1646  };
1647  BLOCK.prototype = {
1648      /**
1649       * A content place holder used when the block has been docked.
1650       * @property contentplaceholder
1651       * @protected
1652       * @type Node
1653       */
1654      contentplaceholder: null,
1655      /**
1656       * The skip link associated with this block.
1657       * @property contentskipanchor
1658       * @protected
1659       * @type Node
1660       */
1661      contentskipanchor: null,
1662      /**
1663       * The cached content node for the actual block
1664       * @property cachedcontentnode
1665       * @protected
1666       * @type Node
1667       */
1668      cachedcontentnode: null,
1669      /**
1670       * If true the user preference isn't updated
1671       * @property skipsetposition
1672       * @protected
1673       * @type Boolean
1674       */
1675      skipsetposition: true,
1676      /**
1677       * The dock item associated with this block
1678       * @property dockitem
1679       * @protected
1680       * @type DOCKEDITEM
1681       */
1682      dockitem: null,
1683      /**
1684       * Called during the initialisation process of the object.
1685       * @method initializer
1686       */
1687      initializer: function() {
1688          var node = Y.one('#inst' + this.get('id'));
1689          if (!node) {
1690              return false;
1691          }
1692  
1693  
1694          M.core.dock.ensureMoveToIconExists(node);
1695  
1696          // Move the block straight to the dock if required
1697          if (node.hasClass(CSS.dockonload)) {
1698              node.removeClass(CSS.dockonload);
1699              this.moveToDock();
1700          }
1701          this.skipsetposition = false;
1702          return true;
1703      },
1704      /**
1705       * Returns the class associated with this block.
1706       * @method _getBlockClass
1707       * @private
1708       * @param {Node} node
1709       * @return String
1710       */
1711      _getBlockClass: function(node) {
1712          var block = node.getData('block'),
1713              classes,
1714              matches;
1715          if (Y.Lang.isString(block) && block !== '') {
1716              return block;
1717          }
1718          classes = node.getAttribute('className').toString();
1719          matches = /(^| )block_([^ ]+)/.exec(classes);
1720          if (matches) {
1721              return matches[2];
1722          }
1723          return matches;
1724      },
1725  
1726      /**
1727       * This function is responsible for moving a block from the page structure onto the dock.
1728       * @method moveToDock
1729       * @param {EventFacade} e
1730       */
1731      moveToDock: function(e) {
1732          if (e) {
1733              e.halt(true);
1734          }
1735  
1736          var dock = M.core.dock.get(),
1737              id = this.get('id'),
1738              blockcontent = Y.one('#inst' + id).one('.content'),
1739              icon = (window.right_to_left()) ? 't/dock_to_block_rtl' : 't/dock_to_block',
1740              breakchar = (location.href.match(/\?/)) ? '&' : '?',
1741              blocktitle,
1742              blockcommands,
1743              movetoimg,
1744              moveto;
1745  
1746          if (!blockcontent) {
1747              return;
1748          }
1749  
1750  
1751          this.recordBlockState();
1752  
1753          blocktitle = this.cachedcontentnode.one('.title h2').cloneNode(true);
1754  
1755          // Build up the block commands.
1756          // These should not actually added to the DOM.
1757          blockcommands = this.cachedcontentnode.one('.title .commands');
1758          if (blockcommands) {
1759              blockcommands = blockcommands.cloneNode(true);
1760          } else {
1761              blockcommands = Y.Node.create('<div class="commands"></div>');
1762          }
1763          movetoimg = Y.Node.create('<img />').setAttrs({
1764              alt: Y.Escape.html(M.util.get_string('undockitem', 'block')),
1765              title: Y.Escape.html(M.util.get_string('undockblock', 'block', blocktitle.get('innerHTML'))),
1766              src: M.util.image_url(icon, 'moodle')
1767          });
1768          moveto = Y.Node.create('<a class="moveto customcommand requiresjs"></a>').setAttrs({
1769              href: Y.config.win.location.href + breakchar + 'dock=' + id
1770          });
1771          moveto.append(movetoimg);
1772          blockcommands.append(moveto.append(movetoimg));
1773  
1774          // Create a new dock item for the block
1775          this.dockitem = new DOCKEDITEM({
1776              block: this,
1777              dock: dock,
1778              blockinstanceid: id,
1779              title: blocktitle,
1780              contents: blockcontent,
1781              commands: blockcommands,
1782              blockclass: this._getBlockClass(Y.one('#inst' + id))
1783          });
1784          // Register an event so that when it is removed we can put it back as a block
1785          dock.add(this.dockitem);
1786  
1787          if (!this.skipsetposition) {
1788              // save the users preference
1789              M.util.set_user_preference('docked_block_instance_' + id, 1);
1790          }
1791  
1792          this.set('isDocked', true);
1793      },
1794      /**
1795       * Records the block state and adds it to the docks holding area.
1796       * @method recordBlockState
1797       */
1798      recordBlockState: function() {
1799          var id = this.get('id'),
1800              dock = M.core.dock.get(),
1801              node = Y.one('#inst' + id),
1802              skipanchor = node.previous();
1803          // Disable the skip anchor when docking
1804          if (skipanchor.hasClass('skip-block')) {
1805              this.contentskipanchor = skipanchor;
1806              this.contentskipanchor.hide();
1807          }
1808          this.cachedcontentnode = node;
1809          this.contentplaceholder = Y.Node.create('<div class="block_dock_placeholder"></div>');
1810          node.replace(this.contentplaceholder);
1811          dock.addToHoldingArea(node);
1812          node = null;
1813      },
1814  
1815      /**
1816       * This function removes a block from the dock and puts it back into the page structure.
1817       * @method returnToPage
1818       * @return {Boolean}
1819       */
1820      returnToPage: function() {
1821          var id = this.get('id');
1822  
1823  
1824          // Enable the skip anchor when going back to block mode
1825          if (this.contentskipanchor) {
1826              this.contentskipanchor.show();
1827          }
1828  
1829          if (this.cachedcontentnode.one('.header')) {
1830              this.cachedcontentnode.one('.header').insert(this.dockitem.get('contents'), 'after');
1831          } else {
1832              this.cachedcontentnode.insert(this.dockitem.get('contents'));
1833          }
1834  
1835          this.contentplaceholder.replace(this.cachedcontentnode);
1836          this.cachedcontentnode = null;
1837  
1838          M.util.set_user_preference('docked_block_instance_' + id, 0);
1839          this.set('isDocked', false);
1840          return true;
1841      }
1842  };
1843  Y.extend(BLOCK, Y.Base, BLOCK.prototype, {
1844      NAME: 'moodle-core-dock-block',
1845      ATTRS: {
1846          /**
1847           * The block instance ID
1848           * @attribute id
1849           * @writeOnce
1850           * @type Number
1851           */
1852          id: {
1853              writeOnce: 'initOnly',
1854              setter: function(value) {
1855                  return parseInt(value, 10);
1856              }
1857          },
1858          /**
1859           * True if the block has been docked.
1860           * @attribute isDocked
1861           * @default false
1862           * @type Boolean
1863           */
1864          isDocked: {
1865              value: false
1866          }
1867      }
1868  });
1869  /* global LOGNS, DOCKEDITEM */
1870  
1871  /**
1872   * Dock JS.
1873   *
1874   * This file contains the docked item class.
1875   *
1876   * @module moodle-core-dock
1877   */
1878  
1879  /**
1880   * Docked item.
1881   *
1882   * @namespace M.core.dock
1883   * @class DockedItem
1884   * @constructor
1885   * @extends Base
1886   * @uses EventTarget
1887   */
1888  DOCKEDITEM = function() {
1889      DOCKEDITEM.superclass.constructor.apply(this, arguments);
1890  };
1891  DOCKEDITEM.prototype = {
1892      /**
1893       * Set to true if this item is currently being displayed.
1894       * @property active
1895       * @protected
1896       * @type Boolean
1897       */
1898      active: false,
1899      /**
1900       * Called during the initialisation process of the object.
1901       * @method initializer
1902       */
1903      initializer: function() {
1904          var title = this.get('title'),
1905              titlestring,
1906              type;
1907          /**
1908           * Fired before the docked item has been drawn.
1909           * @event dockeditem:drawstart
1910           */
1911          this.publish('dockeditem:drawstart', {prefix: 'dockeditem'});
1912          /**
1913           * Fired after the docked item has been drawn.
1914           * @event dockeditem:drawcomplete
1915           */
1916          this.publish('dockeditem:drawcomplete', {prefix: 'dockeditem'});
1917          /**
1918           * Fired before the docked item is to be shown.
1919           * @event dockeditem:showstart
1920           */
1921          this.publish('dockeditem:showstart', {prefix: 'dockeditem'});
1922          /**
1923           * Fired after the docked item has been shown.
1924           * @event dockeditem:showcomplete
1925           */
1926          this.publish('dockeditem:showcomplete', {prefix: 'dockeditem'});
1927          /**
1928           * Fired before the docked item has been hidden.
1929           * @event dockeditem:hidestart
1930           */
1931          this.publish('dockeditem:hidestart', {prefix: 'dockeditem'});
1932          /**
1933           * Fired after the docked item has been hidden.
1934           * @event dockeditem:hidecomplete
1935           */
1936          this.publish('dockeditem:hidecomplete', {prefix: 'dockeditem'});
1937          /**
1938           * Fired when the docked item is removed from the dock.
1939           * @event dockeditem:itemremoved
1940           */
1941          this.publish('dockeditem:itemremoved', {prefix: 'dockeditem'});
1942          if (title) {
1943              type = title.get('nodeName');
1944              titlestring = title.cloneNode(true);
1945              title = Y.Node.create('<' + type + '></' + type + '>');
1946              title = M.core.dock.fixTitleOrientation(title, titlestring.get('text'));
1947              this.set('title', title);
1948              this.set('titlestring', titlestring);
1949          }
1950      },
1951      /**
1952       * This function draws the item on the dock.
1953       * @method draw
1954       * @return Boolean
1955       */
1956      draw: function() {
1957          var create = Y.Node.create,
1958              dock = this.get('dock'),
1959              count = dock.count,
1960              docktitle,
1961              dockitem,
1962              closeicon,
1963              closeiconimg,
1964              id = this.get('id');
1965  
1966          this.fire('dockeditem:drawstart');
1967  
1968          docktitle = create('<div id="dock_item_' + id + '_title" role="menu" aria-haspopup="true" class="'
1969              + CSS.dockedtitle + '"></div>');
1970          docktitle.append(this.get('title'));
1971          dockitem = create('<div id="dock_item_' + id + '" class="' + CSS.dockeditem + '" tabindex="0" rel="' + id + '"></div>');
1972          if (count === 1) {
1973              dockitem.addClass('firstdockitem');
1974          }
1975          dockitem.append(docktitle);
1976          dock.append(dockitem);
1977  
1978          closeiconimg = create('<img alt="' + M.util.get_string('hidepanel', 'block') +
1979                  '" title="' + M.util.get_string('hidedockpanel', 'block') + '" />');
1980          closeiconimg.setAttribute('src', M.util.image_url('t/dockclose', 'moodle'));
1981          closeicon = create('<span class="hidepanelicon" tabindex="0"></span>').append(closeiconimg);
1982          closeicon.on('forceclose|click', this.hide, this);
1983          closeicon.on('dock:actionkey', this.hide, this, {actions: {enter: true, toggle: true}});
1984          this.get('commands').append(closeicon);
1985  
1986          this.set('dockTitleNode', docktitle);
1987          this.set('dockItemNode', dockitem);
1988  
1989          this.fire('dockeditem:drawcomplete');
1990          return true;
1991      },
1992      /**
1993       * This function toggles makes the item active and shows it.
1994       * @method show
1995       * @return Boolean
1996       */
1997      show: function() {
1998          var dock = this.get('dock'),
1999              panel = dock.getPanel(),
2000              docktitle = this.get('dockTitleNode');
2001  
2002          dock.hideActive();
2003          this.fire('dockeditem:showstart');
2004          panel.setHeader(this.get('titlestring'), this.get('commands'));
2005          panel.setBody(Y.Node.create('<div class="block_' + this.get('blockclass') + ' block_docked"></div>')
2006               .append(this.get('contents')));
2007          if (M.core.actionmenu !== undefined) {
2008              M.core.actionmenu.newDOMNode(panel.get('node'));
2009          }
2010          panel.show();
2011          panel.correctWidth();
2012  
2013          this.active = true;
2014          // Add active item class first up
2015          docktitle.addClass(CSS.activeitem);
2016          // Set aria-exapanded property to true.
2017          docktitle.set('aria-expanded', "true");
2018          this.fire('dockeditem:showcomplete');
2019          dock.resize();
2020          return true;
2021      },
2022      /**
2023       * This function hides the item and makes it inactive.
2024       * @method hide
2025       */
2026      hide: function() {
2027          this.fire('dockeditem:hidestart');
2028          if (this.active) {
2029              // No longer active
2030              this.active = false;
2031              // Hide the panel
2032              this.get('dock').getPanel().hide();
2033          }
2034          // Remove the active class
2035          // Set aria-exapanded property to false
2036          this.get('dockTitleNode').removeClass(CSS.activeitem).set('aria-expanded', "false");
2037          this.fire('dockeditem:hidecomplete');
2038      },
2039      /**
2040       * A toggle between calling show and hide functions based on css.activeitem
2041       * Applies rules to key press events (dock:actionkey)
2042       * @method toggle
2043       * @param {String} action
2044       */
2045      toggle: function(action) {
2046          var docktitle = this.get('dockTitleNode');
2047          if (docktitle.hasClass(CSS.activeitem) && action !== 'expand') {
2048              this.hide();
2049          } else if (!docktitle.hasClass(CSS.activeitem) && action !== 'collapse') {
2050              this.show();
2051          }
2052      },
2053      /**
2054       * This function removes the node and destroys it's bits.
2055       * @method remove.
2056       */
2057      remove: function() {
2058          this.hide();
2059          // Return the block to its original position.
2060          this.get('block').returnToPage();
2061          // Remove the dock item node.
2062          this.get('dockItemNode').remove();
2063          this.fire('dockeditem:itemremoved');
2064      },
2065      /**
2066       * Returns the description of this item to use for log calls.
2067       * @method _getLogDescription
2068       * @private
2069       * @return {String}
2070       */
2071      _getLogDescription: function() {
2072          return this.get('titlestring').get('innerHTML') + ' (' + this.get('blockinstanceid') + ')';
2073      }
2074  };
2075  Y.extend(DOCKEDITEM, Y.Base, DOCKEDITEM.prototype, {
2076      NAME: 'moodle-core-dock-dockeditem',
2077      ATTRS: {
2078          /**
2079           * The block this docked item is associated with.
2080           * @attribute block
2081           * @type BLOCK
2082           * @writeOnce
2083           * @required
2084           */
2085          block: {
2086              writeOnce: 'initOnly'
2087          },
2088          /**
2089           * The dock itself.
2090           * @attribute dock
2091           * @type DOCK
2092           * @writeOnce
2093           * @required
2094           */
2095          dock: {
2096              writeOnce: 'initOnly'
2097          },
2098          /**
2099           * The docked item ID. This will be given by the dock.
2100           * @attribute id
2101           * @type Number
2102           */
2103          id: {},
2104          /**
2105           * Block instance id.Taken from the associated block.
2106           * @attribute blockinstanceid
2107           * @type Number
2108           * @writeOnce
2109           */
2110          blockinstanceid: {
2111              writeOnce: 'initOnly',
2112              setter: function(value) {
2113                  return parseInt(value, 10);
2114              }
2115          },
2116          /**
2117           * The title  nodeof the docked item.
2118           * @attribute title
2119           * @type Node
2120           * @default null
2121           */
2122          title: {
2123              value: null
2124          },
2125          /**
2126           * The title string.
2127           * @attribute titlestring
2128           * @type String
2129           */
2130          titlestring: {
2131              value: null
2132          },
2133          /**
2134           * The contents of the docked item
2135           * @attribute contents
2136           * @type Node
2137           * @writeOnce
2138           * @required
2139           */
2140          contents: {
2141              writeOnce: 'initOnly'
2142          },
2143          /**
2144           * Commands associated with the block.
2145           * @attribute commands
2146           * @type Node
2147           * @writeOnce
2148           * @required
2149           */
2150          commands: {
2151              writeOnce: 'initOnly'
2152          },
2153          /**
2154           * The block class.
2155           * @attribute blockclass
2156           * @type String
2157           * @writeOnce
2158           * @required
2159           */
2160          blockclass: {
2161              writeOnce: 'initOnly'
2162          },
2163          /**
2164           * The title node for the docked block.
2165           * @attribute dockTitleNode
2166           * @type Node
2167           */
2168          dockTitleNode: {
2169              value: null
2170          },
2171          /**
2172           * The item node for the docked block.
2173           * @attribute dockItemNode
2174           * @type Node
2175           */
2176          dockItemNode: {
2177              value: null
2178          },
2179          /**
2180           * The container for the docked item (will contain the block contents when visible)
2181           * @attribute dockcontainerNode
2182           * @type Node
2183           */
2184          dockcontainerNode: {
2185              value: null
2186          }
2187      }
2188  });
2189  Y.augment(DOCKEDITEM, Y.EventTarget);
2190  
2191  
2192  }, '@VERSION@', {
2193      "requires": [
2194          "base",
2195          "node",
2196          "event-custom",
2197          "event-mouseenter",
2198          "event-resize",
2199          "escape",
2200          "moodle-core-dock-loader",
2201          "moodle-core-event"
2202      ]
2203  });


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1