[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yui/src/dock/js/ -> dock.js (source)

   1  /**
   2   * Dock JS.
   3   *
   4   * This file contains the DOCK object and all dock related global namespace methods and properties.
   5   *
   6   * @module moodle-core-dock
   7   */
   8  
   9  var LOGNS = 'moodle-core-dock',
  10      BODY = Y.one(Y.config.doc.body),
  11      CSS = {
  12          dock: 'dock',                    // CSS Class applied to the dock box
  13          dockspacer: 'dockspacer',        // CSS class applied to the dockspacer
  14          controls: 'controls',            // CSS class applied to the controls box
  15          body: 'has_dock',                // CSS class added to the body when there is a dock
  16          buttonscontainer: 'buttons_container',
  17          dockeditem: 'dockeditem',        // CSS class added to each item in the dock
  18          dockeditemcontainer: 'dockeditem_container',
  19          dockedtitle: 'dockedtitle',      // CSS class added to the item's title in each dock
  20          activeitem: 'activeitem',        // CSS class added to the active item
  21          dockonload: 'dock_on_load'
  22      },
  23      SELECTOR = {
  24          dockableblock: '.block[data-instanceid][data-dockable]',
  25          blockmoveto: '.block[data-instanceid][data-dockable] .moveto',
  26          panelmoveto: '#dockeditempanel .commands a.moveto',
  27          dockonload: '.block.' + CSS.dockonload,
  28          blockregion: '[data-blockregion]'
  29      },
  30      DOCK,
  31      DOCKPANEL,
  32      TABHEIGHTMANAGER,
  33      BLOCK,
  34      DOCKEDITEM; // eslint-disable-line no-unused-vars
  35  
  36  M.core = M.core || {};
  37  M.core.dock = M.core.dock || {};
  38  
  39  /**
  40   * The dock - once initialised.
  41   *
  42   * @private
  43   * @property _dock
  44   * @type DOCK
  45   */
  46  M.core.dock._dock = null;
  47  
  48  /**
  49   * An associative array of dockable blocks.
  50   * @property _dockableblocks
  51   * @type {Array} An array of BLOCK objects organised by instanceid.
  52   * @private
  53   */
  54  M.core.dock._dockableblocks = {};
  55  
  56  /**
  57   * Initialises the dock.
  58   * This method registers dockable blocks, and creates delegations to dock them.
  59   * @static
  60   * @method init
  61   */
  62  M.core.dock.init = function() {
  63      Y.all(SELECTOR.dockableblock).each(M.core.dock.registerDockableBlock);
  64      Y.Global.on(M.core.globalEvents.BLOCK_CONTENT_UPDATED, function(e) {
  65          M.core.dock.notifyBlockChange(e.instanceid);
  66      }, this);
  67      BODY.delegate('click', M.core.dock.dockBlock, SELECTOR.blockmoveto);
  68      BODY.delegate('key', M.core.dock.dockBlock, SELECTOR.blockmoveto, 'enter');
  69  };
  70  
  71  /**
  72   * Returns an instance of the dock.
  73   * Initialises one if one hasn't already being initialised.
  74   *
  75   * @static
  76   * @method get
  77   * @return DOCK
  78   */
  79  M.core.dock.get = function() {
  80      if (this._dock === null) {
  81          this._dock = new DOCK();
  82      }
  83      return this._dock;
  84  };
  85  
  86  /**
  87   * Registers a dockable block with the dock.
  88   *
  89   * @static
  90   * @method registerDockableBlock
  91   * @param {int} id The block instance ID.
  92   * @return void
  93   */
  94  M.core.dock.registerDockableBlock = function(id) {
  95      if (typeof id === 'object' && typeof id.getData === 'function') {
  96          id = id.getData('instanceid');
  97      }
  98      M.core.dock._dockableblocks[id] = new BLOCK({id: id});
  99  };
 100  
 101  /**
 102   * Docks a block given either its instanceid, its node, or an event fired from within the block.
 103   * @static
 104   * @method dockBlockByInstanceID
 105   * @param id
 106   * @return void
 107   */
 108  M.core.dock.dockBlock = function(id) {
 109      if (typeof id === 'object' && id.target !== 'undefined') {
 110          id = id.target;
 111      }
 112      if (typeof id === "object") {
 113          if (!id.test(SELECTOR.dockableblock)) {
 114              id = id.ancestor(SELECTOR.dockableblock);
 115          }
 116          if (typeof id === 'object' && typeof id.getData === 'function' && !id.ancestor('.' + CSS.dock)) {
 117              id = id.getData('instanceid');
 118          } else {
 119              Y.log('Invalid instanceid given to dockBlockByInstanceID', 'warn', LOGNS);
 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          verticaldirection = M.util.get_string('thisdirectionvertical', 'langconfig');
 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          verticaldirection = 'ver';
 158      }
 159  
 160      switch (verticaldirection) {
 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:' +
 182              fontsize + ';">' + text + '</span></h2>');
 183      BODY.insert(test, 0);
 184      width = test.one('span').get('offsetWidth') * 1.2;
 185      height = test.one('span').get('offsetHeight');
 186      test.remove();
 187  
 188      title.set('innerHTML', text);
 189      title.addClass('css3transform');
 190  
 191      // Move the title into position
 192      title.setStyles({
 193          'position': 'relative',
 194          'fontSize': fontsize,
 195          'width': width,
 196          'top': (width - height) / 2
 197      });
 198  
 199      // Positioning is different when in RTL mode.
 200      if (window.right_to_left()) {
 201          title.setStyle('left', width / 2 - height);
 202      } else {
 203          title.setStyle('right', width / 2 - height);
 204      }
 205  
 206      // Rotate the text
 207      title.setStyles({
 208          'transform': transform,
 209          '-ms-transform': transform,
 210          '-moz-transform': transform,
 211          '-webkit-transform': transform,
 212          '-o-transform': transform
 213      });
 214  
 215      container = Y.Node.create('<div></div>');
 216      container.append(title);
 217      container.setStyles({
 218          height: width + (width / 4),
 219          position: 'relative'
 220      });
 221      return container;
 222  };
 223  
 224  /**
 225   * Informs the dock that the content of the block has changed.
 226   * This should be called by the blocks JS code if its content has been updated dynamically.
 227   * This method ensure the dock resizes if need be.
 228   *
 229   * @static
 230   * @method notifyBlockChange
 231   * @param {Number} instanceid
 232   * @return void
 233   */
 234  M.core.dock.notifyBlockChange = function(instanceid) {
 235      if (this._dock !== null) {
 236          var dock = M.core.dock.get(),
 237              activeitem = dock.getActiveItem();
 238          if (activeitem && activeitem.get('blockinstanceid') === parseInt(instanceid, 10)) {
 239              dock.resizePanelIfRequired();
 240          }
 241      }
 242  };
 243  
 244  /**
 245   * The Dock.
 246   *
 247   * @namespace M.core.dock
 248   * @class Dock
 249   * @constructor
 250   * @extends Base
 251   * @uses EventTarget
 252   */
 253  DOCK = function() {
 254      DOCK.superclass.constructor.apply(this, arguments);
 255  };
 256  DOCK.prototype = {
 257      /**
 258       * Tab height manager used to ensure tabs are always visible.
 259       * @protected
 260       * @property tabheightmanager
 261       * @type TABHEIGHTMANAGER
 262       */
 263      tabheightmanager: null,
 264      /**
 265       * Will be an eventtype if there is an eventype to prevent.
 266       * @protected
 267       * @property preventevent
 268       * @type String
 269       */
 270      preventevent: null,
 271      /**
 272       * Will be an object if there is a delayed event in effect.
 273       * @protected
 274       * @property delayedevent
 275       * @type {Object}
 276       */
 277      delayedevent: null,
 278      /**
 279       * An array of currently docked items.
 280       * @protected
 281       * @property dockeditems
 282       * @type Array
 283       */
 284      dockeditems: [],
 285      /**
 286       * Set to true once the dock has been drawn.
 287       * @protected
 288       * @property dockdrawn
 289       * @type Boolean
 290       */
 291      dockdrawn: false,
 292      /**
 293       * The number of blocks that are currently docked.
 294       * @protected
 295       * @property count
 296       * @type Number
 297       */
 298      count: 0,
 299      /**
 300       * The total number of blocks that have been docked.
 301       * @protected
 302       * @property totalcount
 303       * @type Number
 304       */
 305      totalcount: 0,
 306      /**
 307       * A hidden node used as a holding area for DOM objects used by blocks that have been docked.
 308       * @protected
 309       * @property holdingareanode
 310       * @type Node
 311       */
 312      holdingareanode: null,
 313      /**
 314       * Called during the initialisation process of the object.
 315       * @method initializer
 316       */
 317      initializer: function() {
 318          Y.log('Dock initialising', 'debug', LOGNS);
 319  
 320          // Publish the events the dock has
 321          /**
 322           * Fired when the dock first starts initialising.
 323           * @event dock:starting
 324           */
 325          this.publish('dock:starting', {prefix: 'dock', broadcast:  2, emitFacade: true, fireOnce: true});
 326          /**
 327           * Fired after the dock is initialised for the first time.
 328           * @event dock:initialised
 329           */
 330          this.publish('dock:initialised', {prefix: 'dock', broadcast:  2, emitFacade: true, fireOnce: true});
 331          /**
 332           * Fired before the dock structure and content is first created.
 333           * @event dock:beforedraw
 334           */
 335          this.publish('dock:beforedraw', {prefix: 'dock', fireOnce: true});
 336          /**
 337           * Fired before the dock is changed from hidden to visible.
 338           * @event dock:beforeshow
 339           */
 340          this.publish('dock:beforeshow', {prefix: 'dock'});
 341          /**
 342           * Fires after the dock has been changed from hidden to visible.
 343           * @event dock:shown
 344           */
 345          this.publish('dock:shown', {prefix: 'dock', broadcast: 2});
 346          /**
 347           * Fired after the dock has been changed from visible to hidden.
 348           * @event dock:hidden
 349           */
 350          this.publish('dock:hidden', {prefix: 'dock', broadcast: 2});
 351          /**
 352           * Fires when an item is added to the dock.
 353           * @event dock:itemadded
 354           */
 355          this.publish('dock:itemadded', {prefix: 'dock'});
 356          /**
 357           * Fires when an item is removed from the dock.
 358           * @event dock:itemremoved
 359           */
 360          this.publish('dock:itemremoved', {prefix: 'dock'});
 361          /**
 362           * Fires when a block is added or removed from the dock.
 363           * This happens after the itemadded and itemremoved events have been called.
 364           * @event dock:itemschanged
 365           */
 366          this.publish('dock:itemschanged', {prefix: 'dock', broadcast: 2});
 367          /**
 368           * Fires once when the docks panel is first initialised.
 369           * @event dock:panelgenerated
 370           */
 371          this.publish('dock:panelgenerated', {prefix: 'dock', fireOnce: true});
 372          /**
 373           * Fires when the dock panel is about to be resized.
 374           * @event dock:panelresizestart
 375           */
 376          this.publish('dock:panelresizestart', {prefix: 'dock'});
 377          /**
 378           * Fires after the dock panel has been resized.
 379           * @event dock:resizepanelcomplete
 380           */
 381          this.publish('dock:resizepanelcomplete', {prefix: 'dock'});
 382  
 383          // Apply theme customisations here before we do any real work.
 384          this._applyThemeCustomisation();
 385          // Inform everyone we are now about to initialise.
 386          this.fire('dock:starting');
 387          this._ensureDockDrawn();
 388          // Inform everyone the dock has been initialised
 389          this.fire('dock:initialised');
 390      },
 391      /**
 392       * Ensures that the dock has been drawn.
 393       * @private
 394       * @method _ensureDockDrawn
 395       * @return {Boolean}
 396       */
 397      _ensureDockDrawn: function() {
 398          if (this.dockdrawn === true) {
 399              return true;
 400          }
 401          var dock = this._initialiseDockNode(),
 402              clickargs = {
 403                  cssselector: '.' + CSS.dockedtitle,
 404                  delay: 0
 405              },
 406              mouseenterargs = {
 407                  cssselector: '.' + CSS.dockedtitle,
 408                  delay: 0.5,
 409                  iscontained: true,
 410                  preventevent: 'click',
 411                  preventdelay: 3
 412              };
 413          if (Y.UA.ie > 0 && Y.UA.ie < 7) {
 414              // Adjust for IE 6 (can't handle fixed pos)
 415              dock.setStyle('height', dock.get('winHeight') + 'px');
 416          }
 417  
 418          this.fire('dock:beforedraw');
 419  
 420          this._initialiseDockControls();
 421  
 422          this.tabheightmanager = new TABHEIGHTMANAGER({dock: this});
 423  
 424          // Attach the required event listeners
 425          // We use delegate here as that way a handful of events are created for the dock
 426          // and all items rather than the same number for the dock AND every item individually
 427          Y.delegate('click', this.handleEvent, this.get('dockNode'), '.' + CSS.dockedtitle, this, clickargs);
 428          Y.delegate('mouseenter', this.handleEvent, this.get('dockNode'), '.' + CSS.dockedtitle, this, mouseenterargs);
 429          this.get('dockNode').on('mouseleave', this.handleEvent, this, {cssselector: '#dock', delay: 0.5, iscontained: false});
 430  
 431          Y.delegate('click', this.handleReturnToBlock, this.get('dockNode'), SELECTOR.panelmoveto, this);
 432          Y.delegate('dock:actionkey', this.handleDockedItemEvent, this.get('dockNode'), '.' + CSS.dockeditem, this);
 433  
 434          BODY.on('click', this.handleEvent, this, {cssselector: 'body', delay: 0});
 435          this.on('dock:itemschanged', this.resizeBlockSpace, this);
 436          this.on('dock:itemschanged', this.checkDockVisibility, this);
 437          this.on('dock:itemschanged', this.resetFirstItem, this);
 438          this.dockdrawn = true;
 439          return true;
 440      },
 441      /**
 442       * Handles an actionkey event on the dock.
 443       * @param {EventFacade} e
 444       * @method handleDockedItemEvent
 445       * @return {Boolean}
 446       */
 447      handleDockedItemEvent: function(e) {
 448          if (e.type !== 'dock:actionkey') {
 449              return false;
 450          }
 451          var target = e.target,
 452              dockeditem = '.' + CSS.dockeditem;
 453          if (!target.test(dockeditem)) {
 454              target = target.ancestor(dockeditem);
 455          }
 456          if (!target) {
 457              return false;
 458          }
 459          e.halt();
 460          this.dockeditems[target.getAttribute('rel')].toggle(e.action);
 461      },
 462      /**
 463       * Call the theme customisation method "customise_dock_for_theme" if it exists.
 464       * @private
 465       * @method _applyThemeCustomisation
 466       */
 467      _applyThemeCustomisation: function() {
 468          // Check if there is a customisation function
 469          if (typeof (customise_dock_for_theme) === 'function') {
 470              // First up pre the legacy object.
 471              M.core_dock = this;
 472              M.core_dock.cfg = {
 473                  buffer: null,
 474                  orientation: null,
 475                  position: null,
 476                  spacebeforefirstitem: null,
 477                  removeallicon: null
 478              };
 479              M.core_dock.css = {
 480                  dock: null,
 481                  dockspacer: null,
 482                  controls: null,
 483                  body: null,
 484                  buttonscontainer: null,
 485                  dockeditem: null,
 486                  dockeditemcontainer: null,
 487                  dockedtitle: null,
 488                  activeitem: null
 489              };
 490              try {
 491                  // Run the customisation function
 492                  window.customise_dock_for_theme(this);
 493              } catch (exception) {
 494                  // Do nothing at the moment.
 495                  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 +
 520                              ' is now dock.set(\'' + key + '\', value)',
 521                              'debug', LOGNS);
 522                      this.set(cfgmap[key], value);
 523                  }
 524              }
 525              // Check for and apply any legacy CSS changes..
 526              for (key in M.core_dock.css) {
 527                  if (Y.Lang.isString(key)) {
 528                      value = M.core_dock.css[key];
 529                      if (value === null) {
 530                          continue;
 531                      }
 532                      if (!warned) {
 533                          Y.log('Warning: customise_dock_for_theme has changed. Please update your code.', 'warn', LOGNS);
 534                          warned = true;
 535                      }
 536                      // Damn, they've set something.
 537                      Y.log('Note for customise_dock_for_theme code: M.core_dock.css.' + key + ' is now CSS.' + key + ' = value',
 538                              'debug', LOGNS);
 539                      CSS[key] = value;
 540                  }
 541              }
 542          }
 543      },
 544      /**
 545       * Initialises the dock node, creating it and its content if required.
 546       *
 547       * @private
 548       * @method _initialiseDockNode
 549       * @return {Node} The dockNode
 550       */
 551      _initialiseDockNode: function() {
 552          var dock = this.get('dockNode'),
 553              positionorientationclass = CSS.dock + '_' + this.get('position') + '_' + this.get('orientation'),
 554              holdingarea = Y.Node.create('<div></div>').setStyles({display: 'none'}),
 555              buttons = this.get('buttonsNode'),
 556              container = this.get('itemContainerNode');
 557  
 558          if (!dock) {
 559              dock = Y.one('#' + CSS.dock);
 560          }
 561          if (!dock) {
 562              dock = Y.Node.create('<div id="' + CSS.dock + '"></div>');
 563              BODY.append(dock);
 564          }
 565          dock.setAttribute('role', 'menubar').addClass(positionorientationclass);
 566          if (Y.all(SELECTOR.dockonload).size() === 0) {
 567              // Nothing on the dock... hide it using CSS
 568              dock.addClass('nothingdocked');
 569          } else {
 570              positionorientationclass = CSS.body + '_' + this.get('position') + '_' + this.get('orientation');
 571              BODY.addClass(CSS.body).addClass();
 572          }
 573  
 574          if (!buttons) {
 575              buttons = dock.one('.' + CSS.buttonscontainer);
 576          }
 577          if (!buttons) {
 578              buttons = Y.Node.create('<div class="' + CSS.buttonscontainer + '"></div>');
 579              dock.append(buttons);
 580          }
 581  
 582          if (!container) {
 583              container = dock.one('.' + CSS.dockeditemcontainer);
 584          }
 585          if (!container) {
 586              container = Y.Node.create('<div class="' + CSS.dockeditemcontainer + '"></div>');
 587              buttons.append(container);
 588          }
 589  
 590          BODY.append(holdingarea);
 591          this.holdingareanode = holdingarea;
 592  
 593          this.set('dockNode', dock);
 594          this.set('buttonsNode', buttons);
 595          this.set('itemContainerNode', container);
 596  
 597          return dock;
 598      },
 599      /**
 600       * Initialises the dock controls.
 601       *
 602       * @private
 603       * @method _initialiseDockControls
 604       */
 605      _initialiseDockControls: function() {
 606          // Add a removeall button
 607          // Must set the image src seperatly of we get an error with XML strict headers
 608  
 609          var removeall = Y.Node.create('<img alt="' + M.util.get_string('undockall', 'block') + '" tabindex="0" />');
 610          removeall.setAttribute('src', this.get('undockAllIconUrl'));
 611          removeall.on('removeall|click', this.removeAll, this);
 612          removeall.on('dock:actionkey', this.removeAll, this, {actions: {enter: true}});
 613          this.get('buttonsNode').append(Y.Node.create('<div class="' + CSS.controls + '"></div>').append(removeall));
 614      },
 615      /**
 616       * Returns the dock panel. Initialising it if it hasn't already been initialised.
 617       * @method getPanel
 618       * @return {DOCKPANEL}
 619       */
 620      getPanel: function() {
 621          var panel = this.get('panel');
 622          if (!panel) {
 623              panel = new DOCKPANEL({dock: this});
 624              panel.on('panel:visiblechange', this.resize, this);
 625              Y.on('windowresize', this.resize, this);
 626              // Initialise the dockpanel .. should only happen once
 627              this.set('panel', panel);
 628              this.fire('dock:panelgenerated');
 629          }
 630          return panel;
 631      },
 632      /**
 633       * Resizes the dock panel if required.
 634       * @method resizePanelIfRequired
 635       */
 636      resizePanelIfRequired: function() {
 637          this.resize();
 638          var panel = this.get('panel');
 639          if (panel) {
 640              panel.correctWidth();
 641          }
 642      },
 643      /**
 644       * Handles a dock event sending it to the right place.
 645       *
 646       * @method handleEvent
 647       * @param {EventFacade} e
 648       * @param {Object} options
 649       * @return {Boolean}
 650       */
 651      handleEvent: function(e, options) {
 652          var item = this.getActiveItem(),
 653              target,
 654              targetid,
 655              regex = /^dock_item_(\d+)_title$/,
 656              self = this;
 657          if (options.cssselector === 'body') {
 658              if (!this.get('dockNode').contains(e.target)) {
 659                  if (item) {
 660                      item.hide();
 661                  }
 662              }
 663          } else {
 664              if (e.target.test(options.cssselector)) {
 665                  target = e.target;
 666              } else {
 667                  target = e.target.ancestor(options.cssselector);
 668              }
 669              if (!target) {
 670                  return true;
 671              }
 672              if (this.preventevent !== null && e.type === this.preventevent) {
 673                  return true;
 674              }
 675              if (options.preventevent) {
 676                  this.preventevent = options.preventevent;
 677                  if (options.preventdelay) {
 678                      setTimeout(function() {
 679                          self.preventevent = null;
 680                      }, options.preventdelay * 1000);
 681                  }
 682              }
 683              if (this.delayedevent && this.delayedevent.timeout) {
 684                  clearTimeout(this.delayedevent.timeout);
 685                  this.delayedevent.event.detach();
 686                  this.delayedevent = null;
 687              }
 688              if (options.delay > 0) {
 689                  return this.delayEvent(e, options, target);
 690              }
 691              targetid = target.get('id');
 692              if (targetid.match(regex)) {
 693                  item = this.dockeditems[targetid.replace(regex, '$1')];
 694                  if (item.active) {
 695                      item.hide();
 696                  } else {
 697                      item.show();
 698                  }
 699              } else if (item) {
 700                  item.hide();
 701              }
 702          }
 703          return true;
 704      },
 705      /**
 706       * Delays an event.
 707       *
 708       * @method delayEvent
 709       * @param {EventFacade} event
 710       * @param {Object} options
 711       * @param {Node} target
 712       * @return {Boolean}
 713       */
 714      delayEvent: function(event, options, target) {
 715          var self = this;
 716          self.delayedevent = (function() {
 717              return {
 718                  target: target,
 719                  event: BODY.on('mousemove', function(e) {
 720                      self.delayedevent.target = e.target;
 721                  }),
 722                  timeout: null
 723              };
 724          })(self);
 725          self.delayedevent.timeout = setTimeout(function() {
 726              self.delayedevent.timeout = null;
 727              self.delayedevent.event.detach();
 728              if (options.iscontained === self.get('dockNode').contains(self.delayedevent.target)) {
 729                  self.handleEvent(event, {cssselector: options.cssselector, delay: 0, iscontained: options.iscontained});
 730              }
 731          }, options.delay * 1000);
 732          return true;
 733      },
 734      /**
 735       * Resizes block spaces.
 736       * @method resizeBlockSpace
 737       */
 738      resizeBlockSpace: function() {
 739          if (Y.all(SELECTOR.dockonload).size() > 0) {
 740              // Do not resize during initial load
 741              return;
 742          }
 743  
 744          var populatedRegionCount = 0,
 745              populatedBlockRegions = [],
 746              unpopulatedBlockRegions = [],
 747              isMoving = false,
 748              populatedLegacyRegions = [],
 749              containsLegacyRegions = false,
 750              classesToAdd = [],
 751              classesToRemove = [];
 752  
 753          // First look for understood regions.
 754          Y.all(SELECTOR.blockregion).each(function(region) {
 755              var regionname = region.getData('blockregion');
 756              if (region.all('.block').size() > 0) {
 757                  populatedBlockRegions.push(regionname);
 758                  populatedRegionCount++;
 759              } else if (region.all('.block_dock_placeholder').size() > 0) {
 760                  unpopulatedBlockRegions.push(regionname);
 761              }
 762          });
 763  
 764          // Next check for legacy regions.
 765          Y.all('.block-region').each(function(region) {
 766              if (region.test(SELECTOR.blockregion)) {
 767                  // This is a new region, we've already processed it.
 768                  return;
 769              }
 770  
 771              // Sigh - there are legacy regions.
 772              containsLegacyRegions = true;
 773  
 774              var regionname = region.get('id').replace(/^region\-/, 'side-'),
 775                  hasblocks = (region.all('.block').size() > 0);
 776  
 777              if (hasblocks) {
 778                  populatedLegacyRegions.push(regionname);
 779                  populatedRegionCount++;
 780              } else {
 781                  // This legacy region has no blocks so cannot have the -only body tag.
 782                  classesToRemove.push(
 783                          regionname + '-only'
 784                      );
 785              }
 786          });
 787  
 788          if (BODY.hasClass('blocks-moving')) {
 789              // When we're moving blocks, we do not want to collapse.
 790              isMoving = true;
 791          }
 792  
 793          Y.each(unpopulatedBlockRegions, function(regionname) {
 794              classesToAdd.push(
 795                      // This block region is empty.
 796                      'empty-region-' + regionname,
 797  
 798                      // Which has the same effect as being docked.
 799                      'docked-region-' + regionname
 800                  );
 801              classesToRemove.push(
 802                      // It is no-longer used.
 803                      'used-region-' + regionname,
 804  
 805                      // It cannot be the only region on screen if it is empty.
 806                      regionname + '-only'
 807                  );
 808          }, this);
 809  
 810          Y.each(populatedBlockRegions, function(regionname) {
 811              classesToAdd.push(
 812                      // This block region is in use.
 813                      'used-region-' + regionname
 814                  );
 815              classesToRemove.push(
 816                      // It is not empty.
 817                      'empty-region-' + regionname,
 818  
 819                      // Is it not docked.
 820                      'docked-region-' + regionname
 821                  );
 822  
 823              if (populatedRegionCount === 1 && isMoving === false) {
 824                  // There was only one populated region, and we are not moving blocks.
 825                  classesToAdd.push(regionname + '-only');
 826              } else {
 827                  // There were multiple block regions visible - remove any 'only' classes.
 828                  classesToRemove.push(regionname + '-only');
 829              }
 830          }, this);
 831  
 832          if (containsLegacyRegions) {
 833              // Handle the classing for legacy blocks. These have slightly different class names for the body.
 834              if (isMoving || populatedRegionCount !== 1) {
 835                  Y.each(populatedLegacyRegions, function(regionname) {
 836                      classesToRemove.push(regionname + '-only');
 837                  });
 838              } else {
 839                  Y.each(populatedLegacyRegions, function(regionname) {
 840                      classesToAdd.push(regionname + '-only');
 841                  });
 842              }
 843          }
 844  
 845          if (!BODY.hasClass('has-region-content')) {
 846              // This page does not have a content region, therefore content-only is implied when all block regions are docked.
 847              if (populatedRegionCount === 0 && isMoving === false) {
 848                  // If all blocks are docked, ensure that the content-only class is added anyway.
 849                  classesToAdd.push('content-only');
 850              } else {
 851                  // Otherwise remove it.
 852                  classesToRemove.push('content-only');
 853              }
 854          }
 855  
 856          // Modify the body clases.
 857          Y.each(classesToRemove, function(className) {
 858              BODY.removeClass(className);
 859          });
 860          Y.each(classesToAdd, function(className) {
 861              BODY.addClass(className);
 862          });
 863      },
 864      /**
 865       * Adds an item to the dock.
 866       * @method add
 867       * @param {DOCKEDITEM} item
 868       */
 869      add: function(item) {
 870          // Set the dockitem id to the total count and then increment it.
 871          item.set('id', this.totalcount);
 872          Y.log('Adding block ' + item._getLogDescription() + ' to the dock.', 'debug', LOGNS);
 873          this.count++;
 874          this.totalcount++;
 875          this.dockeditems[item.get('id')] = item;
 876          this.dockeditems[item.get('id')].draw();
 877          this.fire('dock:itemadded', item);
 878          this.fire('dock:itemschanged', item);
 879      },
 880      /**
 881       * Appends an item to the dock (putting it in the item container.
 882       * @method append
 883       * @param {Node} docknode
 884       */
 885      append: function(docknode) {
 886          this.get('itemContainerNode').append(docknode);
 887      },
 888      /**
 889       * Handles events that require a docked block to be returned to the page./
 890       * @method handleReturnToBlock
 891       * @param {EventFacade} e
 892       */
 893      handleReturnToBlock: function(e) {
 894          e.halt();
 895          this.remove(this.getActiveItem().get('id'));
 896      },
 897      /**
 898       * Removes a docked item from the dock.
 899       * @method remove
 900       * @param {Number} id The docked item id.
 901       * @return {Boolean}
 902       */
 903      remove: function(id) {
 904          if (!this.dockeditems[id]) {
 905              return false;
 906          }
 907          Y.log('Removing block ' + this.dockeditems[id]._getLogDescription() + ' from the dock.', 'debug', LOGNS);
 908          this.dockeditems[id].remove();
 909          delete this.dockeditems[id];
 910          this.count--;
 911          this.fire('dock:itemremoved', id);
 912          this.fire('dock:itemschanged', id);
 913          return true;
 914      },
 915      /**
 916       * Ensures the the first item in the dock has the correct class.
 917       * @method resetFirstItem
 918       */
 919      resetFirstItem: function() {
 920          this.get('dockNode').all('.' + CSS.dockeditem + '.firstdockitem').removeClass('firstdockitem');
 921          if (this.get('dockNode').one('.' + CSS.dockeditem)) {
 922              this.get('dockNode').one('.' + CSS.dockeditem).addClass('firstdockitem');
 923          }
 924      },
 925      /**
 926       * Removes all docked blocks returning them to the page.
 927       * @method removeAll
 928       * @return {Boolean}
 929       */
 930      removeAll: function() {
 931          Y.log('Undocking all ' + this.dockeditems.length + ' blocks', 'debug', LOGNS);
 932          var i;
 933          for (i in this.dockeditems) {
 934              if (Y.Lang.isNumber(i) || Y.Lang.isString(i)) {
 935                  this.remove(i);
 936              }
 937          }
 938          return true;
 939      },
 940      /**
 941       * Hides the active item.
 942       * @method hideActive
 943       */
 944      hideActive: function() {
 945          var item = this.getActiveItem();
 946          if (item) {
 947              item.hide();
 948          }
 949      },
 950      /**
 951       * Checks wether the dock should be shown or hidden
 952       * @method checkDockVisibility
 953       */
 954      checkDockVisibility: function() {
 955          var bodyclass = CSS.body + '_' + this.get('position') + '_' + this.get('orientation');
 956          if (!this.count) {
 957              this.get('dockNode').addClass('nothingdocked');
 958              BODY.removeClass(CSS.body).removeClass();
 959              this.fire('dock:hidden');
 960          } else {
 961              this.fire('dock:beforeshow');
 962              this.get('dockNode').removeClass('nothingdocked');
 963              BODY.addClass(CSS.body).addClass(bodyclass);
 964              this.fire('dock:shown');
 965          }
 966      },
 967      /**
 968       * This function checks the size and position of the panel and moves/resizes if
 969       * required to keep it within the bounds of the window.
 970       * @method resize
 971       * @return {Boolean}
 972       */
 973      resize: function() {
 974          var panel = this.getPanel(),
 975              item = this.getActiveItem(),
 976              buffer,
 977              screenh,
 978              docky,
 979              titletop,
 980              containery,
 981              containerheight,
 982              scrolltop,
 983              panelheight,
 984              dockx,
 985              titleleft;
 986          if (!panel.get('visible') || !item) {
 987              return true;
 988          }
 989  
 990          this.fire('dock:panelresizestart');
 991          if (this.get('orientation') === 'vertical') {
 992              buffer = this.get('bufferPanel');
 993              screenh = parseInt(BODY.get('winHeight'), 10) - (buffer * 2);
 994              docky = this.get('dockNode').getY();
 995              titletop = item.get('dockTitleNode').getY() - docky - buffer;
 996              containery = this.get('itemContainerNode').getY();
 997              containerheight = containery - docky + this.get('buttonsNode').get('offsetHeight');
 998              scrolltop = panel.get('bodyNode').get('scrollTop');
 999              panel.get('bodyNode').setStyle('height', 'auto');
1000              panel.get('node').removeClass('oversized_content');
1001              panelheight = panel.get('node').get('offsetHeight');
1002  
1003              if (Y.UA.ie > 0 && Y.UA.ie < 7) {
1004                  panel.setTop(item.get('dockTitleNode').getY());
1005              } else if (panelheight > screenh) {
1006                  panel.setTop(buffer - containerheight);
1007                  panel.get('bodyNode').setStyle('height', (screenh - panel.get('headerNode').get('offsetHeight')) + 'px');
1008                  panel.get('node').addClass('oversized_content');
1009              } else if (panelheight > (screenh - (titletop - buffer))) {
1010                  panel.setTop(titletop - containerheight - (panelheight - (screenh - titletop)) + buffer);
1011              } else {
1012                  panel.setTop(titletop - containerheight + buffer);
1013              }
1014  
1015              if (scrolltop) {
1016                  panel.get('bodyNode').set('scrollTop', scrolltop);
1017              }
1018          }
1019  
1020          if (this.get('position') === 'right') {
1021              panel.get('node').setStyle('left', '-' + panel.get('node').get('offsetWidth') + 'px');
1022  
1023          } else if (this.get('position') === 'top') {
1024              dockx = this.get('dockNode').getX();
1025              titleleft = item.get('dockTitleNode').getX() - dockx;
1026              panel.get('node').setStyle('left', titleleft + 'px');
1027          }
1028  
1029          this.fire('dock:resizepanelcomplete');
1030          return true;
1031      },
1032      /**
1033       * Returns the currently active dock item or false
1034       * @method getActiveItem
1035       * @return {DOCKEDITEM}
1036       */
1037      getActiveItem: function() {
1038          var i;
1039          for (i in this.dockeditems) {
1040              if (this.dockeditems[i].active) {
1041                  return this.dockeditems[i];
1042              }
1043          }
1044          return false;
1045      },
1046      /**
1047       * Adds an item to the holding area.
1048       * @method addToHoldingArea
1049       * @param {Node} node
1050       */
1051      addToHoldingArea: function(node) {
1052          this.holdingareanode.append(node);
1053      }
1054  };
1055  
1056  Y.extend(DOCK, Y.Base, DOCK.prototype, {
1057      NAME: 'moodle-core-dock',
1058      ATTRS: {
1059          /**
1060           * The dock itself. #dock.
1061           * @attribute dockNode
1062           * @type Node
1063           * @writeOnce
1064           */
1065          dockNode: {
1066              writeOnce: true
1067          },
1068          /**
1069           * The docks panel.
1070           * @attribute panel
1071           * @type DOCKPANEL
1072           * @writeOnce
1073           */
1074          panel: {
1075              writeOnce: true
1076          },
1077          /**
1078           * A container within the dock used for buttons.
1079           * @attribute buttonsNode
1080           * @type Node
1081           * @writeOnce
1082           */
1083          buttonsNode: {
1084              writeOnce: true
1085          },
1086          /**
1087           * A container within the dock used for docked blocks.
1088           * @attribute itemContainerNode
1089           * @type Node
1090           * @writeOnce
1091           */
1092          itemContainerNode: {
1093              writeOnce: true
1094          },
1095  
1096          /**
1097           * Buffer used when containing a panel.
1098           * @attribute bufferPanel
1099           * @type Number
1100           * @default 10
1101           */
1102          bufferPanel: {
1103              value: 10,
1104              validator: Y.Lang.isNumber
1105          },
1106  
1107          /**
1108           * Position of the dock.
1109           * @attribute position
1110           * @type String
1111           * @default left
1112           */
1113          position: {
1114              value: 'left',
1115              validator: Y.Lang.isString
1116          },
1117  
1118          /**
1119           * vertical || horizontal determines if we change the title
1120           * @attribute orientation
1121           * @type String
1122           * @default vertical
1123           */
1124          orientation: {
1125              value: 'vertical',
1126              validator: Y.Lang.isString,
1127              setter: function(value) {
1128                  if (value.match(/^vertical$/i)) {
1129                      return 'vertical';
1130                  }
1131                  return 'horizontal';
1132              }
1133          },
1134  
1135          /**
1136           * Space between the top of the dock and the first item.
1137           * @attribute bufferBeforeFirstItem
1138           * @type Number
1139           * @default 10
1140           */
1141          bufferBeforeFirstItem: {
1142              value: 10,
1143              validator: Y.Lang.isNumber
1144          },
1145  
1146          /**
1147           * Icon URL for the icon to undock all blocks
1148           * @attribute undockAllIconUrl
1149           * @type String
1150           * @default t/dock_to_block
1151           */
1152          undockAllIconUrl: {
1153              value: M.util.image_url((window.right_to_left()) ? 't/dock_to_block_rtl' : 't/dock_to_block', 'moodle'),
1154              validator: Y.Lang.isString
1155          }
1156      }
1157  });
1158  Y.augment(DOCK, Y.EventTarget);


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