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