[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

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


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