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