[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |