[ Index ]

PHP Cross Reference of moodle-2.8

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


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