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