[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 YUI.add('moodle-core-notification-dialogue', function (Y, NAME) { 2 3 var DIALOGUE_PREFIX, 4 BASE, 5 CONFIRMYES, 6 CONFIRMNO, 7 TITLE, 8 QUESTION, 9 CSS; 10 11 DIALOGUE_PREFIX = 'moodle-dialogue', 12 BASE = 'notificationBase', 13 CONFIRMYES = 'yesLabel', 14 CONFIRMNO = 'noLabel', 15 TITLE = 'title', 16 QUESTION = 'question', 17 CSS = { 18 BASE : 'moodle-dialogue-base', 19 WRAP : 'moodle-dialogue-wrap', 20 HEADER : 'moodle-dialogue-hd', 21 BODY : 'moodle-dialogue-bd', 22 CONTENT : 'moodle-dialogue-content', 23 FOOTER : 'moodle-dialogue-ft', 24 HIDDEN : 'hidden', 25 LIGHTBOX : 'moodle-dialogue-lightbox' 26 }; 27 28 // Set up the namespace once. 29 M.core = M.core || {}; 30 /** 31 * The generic dialogue class for use in Moodle. 32 * 33 * @module moodle-core-notification 34 * @submodule moodle-core-notification-dialogue 35 */ 36 37 var DIALOGUE_NAME = 'Moodle dialogue', 38 DIALOGUE, 39 DIALOGUE_FULLSCREEN_CLASS = DIALOGUE_PREFIX + '-fullscreen', 40 DIALOGUE_HIDDEN_CLASS = DIALOGUE_PREFIX + '-hidden', 41 DIALOGUE_SELECTOR =' [role=dialog]', 42 MENUBAR_SELECTOR = '[role=menubar]', 43 DOT = '.', 44 HAS_ZINDEX = 'moodle-has-zindex', 45 CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]'; 46 47 /** 48 * A re-usable dialogue box with Moodle classes applied. 49 * 50 * @param {Object} c Object literal specifying the dialogue configuration properties. 51 * @constructor 52 * @class M.core.dialogue 53 * @extends Panel 54 */ 55 DIALOGUE = function(c) { 56 var config = Y.clone(c); 57 config.COUNT = Y.stamp(this); 58 var id = 'moodle-dialogue-' + config.COUNT; 59 config.notificationBase = 60 Y.Node.create('<div class="'+CSS.BASE+'">') 61 .append(Y.Node.create('<div id="'+id+'" role="dialog" aria-labelledby="'+id+'-header-text" class="'+CSS.WRAP+'"></div>') 62 .append(Y.Node.create('<div id="'+id+'-header-text" class="'+CSS.HEADER+' yui3-widget-hd"></div>')) 63 .append(Y.Node.create('<div class="'+CSS.BODY+' yui3-widget-bd"></div>')) 64 .append(Y.Node.create('<div class="'+CSS.FOOTER+' yui3-widget-ft"></div>'))); 65 Y.one(document.body).append(config.notificationBase); 66 67 if (config.additionalBaseClass) { 68 config.notificationBase.addClass(config.additionalBaseClass); 69 } 70 71 config.srcNode = '#'+id; 72 73 // closeButton param to keep the stable versions API. 74 if (config.closeButton === false) { 75 config.buttons = null; 76 } else { 77 config.buttons = [ 78 { 79 section: Y.WidgetStdMod.HEADER, 80 classNames: 'closebutton', 81 action: function () { 82 this.hide(); 83 } 84 } 85 ]; 86 } 87 DIALOGUE.superclass.constructor.apply(this, [config]); 88 89 if (config.closeButton !== false) { 90 // The buttons constructor does not allow custom attributes 91 this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle')); 92 } 93 }; 94 Y.extend(DIALOGUE, Y.Panel, { 95 // Window resize event listener. 96 _resizeevent : null, 97 // Orientation change event listener. 98 _orientationevent : null, 99 _calculatedzindex : false, 100 101 /** 102 * The original position of the dialogue before it was reposition to 103 * avoid browser jumping. 104 * 105 * @property _originalPosition 106 * @protected 107 * @type Array 108 */ 109 _originalPosition: null, 110 111 /** 112 * Initialise the dialogue. 113 * 114 * @method initializer 115 */ 116 initializer : function() { 117 var bb; 118 119 if (this.get('render')) { 120 this.render(); 121 } 122 this.after('visibleChange', this.visibilityChanged, this); 123 if (this.get('center')) { 124 this.centerDialogue(); 125 } 126 127 if (this.get('modal')) { 128 this.plug(Y.M.core.LockScroll); 129 } 130 131 // Workaround upstream YUI bug http://yuilibrary.com/projects/yui3/ticket/2532507 132 // and allow setting of z-index in theme. 133 bb = this.get('boundingBox'); 134 bb.addClass(HAS_ZINDEX); 135 136 // Add any additional classes that were specified. 137 Y.Array.each(this.get('extraClasses'), bb.addClass, bb); 138 139 if (this.get('visible')) { 140 this.applyZIndex(); 141 } 142 // Recalculate the zIndex every time the modal is altered. 143 this.on('maskShow', this.applyZIndex); 144 145 this.on('maskShow', function() { 146 // When the mask shows, position the boundingBox at the top-left of the window such that when it is 147 // focused, the position does not change. 148 var w = Y.one(Y.config.win), 149 bb = this.get('boundingBox'); 150 151 if (!this.get('center')) { 152 this._originalPosition = bb.getXY(); 153 } 154 155 if (bb.getStyle('position') !== 'fixed') { 156 // If the boundingBox has been positioned in a fixed manner, then it will not position correctly to scrollTop. 157 bb.setStyles({ 158 top: w.get('scrollTop'), 159 left: w.get('scrollLeft') 160 }); 161 } 162 }, this); 163 164 // Remove the dialogue from the DOM when it is destroyed. 165 this.after('destroyedChange', function(){ 166 this.get(BASE).remove(true); 167 }, this); 168 }, 169 170 /** 171 * Either set the zindex to the supplied value, or set it to one more than the highest existing 172 * dialog in the page. 173 * 174 * @method applyZIndex 175 */ 176 applyZIndex : function() { 177 var highestzindex = 1, 178 zindexvalue = 1, 179 bb = this.get('boundingBox'), 180 ol = this.get('maskNode'), 181 zindex = this.get('zIndex'); 182 if (zindex !== 0 && !this._calculatedzindex) { 183 // The zindex was specified so we should use that. 184 bb.setStyle('zIndex', zindex); 185 } else { 186 // Determine the correct zindex by looking at all existing dialogs and menubars in the page. 187 Y.all(DIALOGUE_SELECTOR + ', ' + MENUBAR_SELECTOR + ', ' + DOT + HAS_ZINDEX).each(function (node) { 188 var zindex = this.findZIndex(node); 189 if (zindex > highestzindex) { 190 highestzindex = zindex; 191 } 192 }, this); 193 // Only set the zindex if we found a wrapper. 194 zindexvalue = (highestzindex + 1).toString(); 195 bb.setStyle('zIndex', zindexvalue); 196 this.set('zIndex', zindexvalue); 197 if (this.get('modal')) { 198 ol.setStyle('zIndex', zindexvalue); 199 200 // In IE8, the z-indexes do not take effect properly unless you toggle 201 // the lightbox from 'fixed' to 'static' and back. This code does so 202 // using the minimum setTimeouts that still actually work. 203 if (Y.UA.ie && Y.UA.compareVersions(Y.UA.ie, 9) < 0) { 204 setTimeout(function() { 205 ol.setStyle('position', 'static'); 206 setTimeout(function() { 207 ol.setStyle('position', 'fixed'); 208 }, 0); 209 }, 0); 210 } 211 } 212 this._calculatedzindex = true; 213 } 214 }, 215 216 /** 217 * Finds the zIndex of the given node or its parent. 218 * 219 * @method findZIndex 220 * @param {Node} node The Node to apply the zIndex to. 221 * @return {Number} Either the zIndex, or 0 if one was not found. 222 */ 223 findZIndex : function(node) { 224 // In most cases the zindex is set on the parent of the dialog. 225 var zindex = node.getStyle('zIndex') || node.ancestor().getStyle('zIndex'); 226 if (zindex) { 227 return parseInt(zindex, 10); 228 } 229 return 0; 230 }, 231 232 /** 233 * Event listener for the visibility changed event. 234 * 235 * @method visibilityChanged 236 * @param {EventFacade} e 237 */ 238 visibilityChanged : function(e) { 239 var titlebar, bb; 240 if (e.attrName === 'visible') { 241 this.get('maskNode').addClass(CSS.LIGHTBOX); 242 if (e.prevVal && !e.newVal) { 243 bb = this.get('boundingBox'); 244 if (this._resizeevent) { 245 this._resizeevent.detach(); 246 this._resizeevent = null; 247 } 248 if (this._orientationevent) { 249 this._orientationevent.detach(); 250 this._orientationevent = null; 251 } 252 bb.detach('key', this.keyDelegation); 253 } 254 if (!e.prevVal && e.newVal) { 255 // This needs to be done each time the dialog is shown as new dialogs may have been opened. 256 this.applyZIndex(); 257 // This needs to be done each time the dialog is shown as the window may have been resized. 258 this.makeResponsive(); 259 if (!this.shouldResizeFullscreen()) { 260 if (this.get('draggable')) { 261 titlebar = '#' + this.get('id') + ' .' + CSS.HEADER; 262 this.plug(Y.Plugin.Drag, {handles : [titlebar]}); 263 Y.one(titlebar).setStyle('cursor', 'move'); 264 } 265 } 266 this.keyDelegation(); 267 } 268 if (this.get('center') && !e.prevVal && e.newVal) { 269 this.centerDialogue(); 270 } 271 } 272 }, 273 /** 274 * If the responsive attribute is set on the dialog, and the window size is 275 * smaller than the responsive width - make the dialog fullscreen. 276 * 277 * @method makeResponsive 278 */ 279 makeResponsive : function() { 280 var bb = this.get('boundingBox'); 281 282 if (this.shouldResizeFullscreen()) { 283 // Make this dialogue fullscreen on a small screen. 284 // Disable the page scrollbars. 285 286 // Size and position the fullscreen dialog. 287 288 bb.addClass(DIALOGUE_FULLSCREEN_CLASS); 289 bb.setStyles({'left' : null, 290 'top' : null, 291 'width' : null, 292 'height' : null, 293 'right' : null, 294 'bottom' : null}); 295 } else { 296 if (this.get('responsive')) { 297 // We must reset any of the fullscreen changes. 298 bb.removeClass(DIALOGUE_FULLSCREEN_CLASS) 299 .setStyles({'width' : this.get('width'), 300 'height' : this.get('height')}); 301 } 302 } 303 }, 304 /** 305 * Center the dialog on the screen. 306 * 307 * @method centerDialogue 308 */ 309 centerDialogue : function() { 310 var bb = this.get('boundingBox'), 311 hidden = bb.hasClass(DIALOGUE_HIDDEN_CLASS), 312 x, 313 y; 314 315 // Don't adjust the position if we are in full screen mode. 316 if (this.shouldResizeFullscreen()) { 317 return; 318 } 319 if (hidden) { 320 bb.setStyle('top', '-1000px').removeClass(DIALOGUE_HIDDEN_CLASS); 321 } 322 x = Math.max(Math.round((bb.get('winWidth') - bb.get('offsetWidth'))/2), 15); 323 y = Math.max(Math.round((bb.get('winHeight') - bb.get('offsetHeight'))/2), 15) + Y.one(window).get('scrollTop'); 324 bb.setStyles({ 'left' : x, 'top' : y}); 325 326 if (hidden) { 327 bb.addClass(DIALOGUE_HIDDEN_CLASS); 328 } 329 this.makeResponsive(); 330 }, 331 /** 332 * Return whether this dialogue should be fullscreen or not. 333 * 334 * Responsive attribute must be true and we should not be in an iframe and the screen width should 335 * be less than the responsive width. 336 * 337 * @method shouldResizeFullscreen 338 * @return {Boolean} 339 */ 340 shouldResizeFullscreen : function() { 341 return (window === window.parent) && this.get('responsive') && 342 Math.floor(Y.one(document.body).get('winWidth')) < this.get('responsiveWidth'); 343 }, 344 345 show: function() { 346 var result = null, 347 header = this.headerNode, 348 content = this.bodyNode, 349 focusSelector = this.get('focusOnShowSelector'), 350 focusNode = null; 351 352 result = DIALOGUE.superclass.show.call(this); 353 354 if (!this.get('center') && this._originalPosition) { 355 // Restore the dialogue position to it's location before it was moved at show time. 356 this.get('boundingBox').setXY(this._originalPosition); 357 } 358 359 // Lock scroll if the plugin is present. 360 if (this.lockScroll) { 361 // We need to force the scroll locking for full screen dialogues, even if they have a small vertical size to 362 // prevent the background scrolling while the dialogue is open. 363 this.lockScroll.enableScrollLock(this.shouldResizeFullscreen()); 364 } 365 366 // Try and find a node to focus on using the focusOnShowSelector attribute. 367 if (focusSelector !== null) { 368 focusNode = this.get('boundingBox').one(focusSelector); 369 } 370 if (!focusNode) { 371 // Fall back to the header or the content if no focus node was found yet. 372 if (header && header !== '') { 373 focusNode = header; 374 } else if (content && content !== '') { 375 focusNode = content; 376 } 377 } 378 if (focusNode) { 379 focusNode.focus(); 380 } 381 return result; 382 }, 383 384 hide: function(e) { 385 if (e) { 386 // If the event was closed by an escape key event, then we need to check that this 387 // dialogue is currently focused to prevent closing all dialogues in the stack. 388 if (e.type === 'key' && e.keyCode === 27 && !this.get('focused')) { 389 return; 390 } 391 } 392 393 // Unlock scroll if the plugin is present. 394 if (this.lockScroll) { 395 this.lockScroll.disableScrollLock(); 396 } 397 398 return DIALOGUE.superclass.hide.call(this, arguments); 399 }, 400 /** 401 * Setup key delegation to keep tabbing within the open dialogue. 402 * 403 * @method keyDelegation 404 */ 405 keyDelegation : function() { 406 var bb = this.get('boundingBox'); 407 bb.delegate('key', function(e){ 408 var target = e.target; 409 var direction = 'forward'; 410 if (e.shiftKey) { 411 direction = 'backward'; 412 } 413 if (this.trapFocus(target, direction)) { 414 e.preventDefault(); 415 } 416 }, 'down:9', CAN_RECEIVE_FOCUS_SELECTOR, this); 417 }, 418 419 /** 420 * Trap the tab focus within the open modal. 421 * 422 * @method trapFocus 423 * @param {string} target the element target 424 * @param {string} direction tab key for forward and tab+shift for backward 425 * @return {Boolean} The result of the focus action. 426 */ 427 trapFocus : function(target, direction) { 428 var bb = this.get('boundingBox'), 429 firstitem = bb.one(CAN_RECEIVE_FOCUS_SELECTOR), 430 lastitem = bb.all(CAN_RECEIVE_FOCUS_SELECTOR).pop(); 431 432 if (target === lastitem && direction === 'forward') { // Tab key. 433 return firstitem.focus(); 434 } else if (target === firstitem && direction === 'backward') { // Tab+shift key. 435 return lastitem.focus(); 436 } 437 } 438 }, { 439 NAME : DIALOGUE_NAME, 440 CSS_PREFIX : DIALOGUE_PREFIX, 441 ATTRS : { 442 notificationBase : { 443 444 }, 445 446 /** 447 * Whether to display the dialogue modally and with a 448 * lightbox style. 449 * 450 * @attribute lightbox 451 * @type Boolean 452 * @default true 453 * @deprecated Since Moodle 2.7. Please use modal instead. 454 */ 455 lightbox: { 456 lazyAdd: false, 457 setter: function(value) { 458 this.set('modal', value); 459 } 460 }, 461 462 /** 463 * Whether to display a close button on the dialogue. 464 * 465 * Note, we do not recommend hiding the close button as this has 466 * potential accessibility concerns. 467 * 468 * @attribute closeButton 469 * @type Boolean 470 * @default true 471 */ 472 closeButton : { 473 validator : Y.Lang.isBoolean, 474 value : true 475 }, 476 477 /** 478 * The title for the close button if one is to be shown. 479 * 480 * @attribute closeButtonTitle 481 * @type String 482 * @default 'Close' 483 */ 484 closeButtonTitle : { 485 validator : Y.Lang.isString, 486 value: M.util.get_string('closebuttontitle', 'moodle') 487 }, 488 489 /** 490 * Whether to display the dialogue centrally on the screen. 491 * 492 * @attribute center 493 * @type Boolean 494 * @default true 495 */ 496 center : { 497 validator : Y.Lang.isBoolean, 498 value : true 499 }, 500 501 /** 502 * Whether to make the dialogue movable around the page. 503 * 504 * @attribute draggable 505 * @type Boolean 506 * @default false 507 */ 508 draggable : { 509 validator : Y.Lang.isBoolean, 510 value : false 511 }, 512 513 /** 514 * Used to generate a unique id for the dialogue. 515 * 516 * @attribute COUNT 517 * @type String 518 * @default null 519 */ 520 COUNT: { 521 value: null 522 }, 523 524 /** 525 * Used to disable the fullscreen resizing behaviour if required. 526 * 527 * @attribute responsive 528 * @type Boolean 529 * @default true 530 */ 531 responsive : { 532 validator : Y.Lang.isBoolean, 533 value : true 534 }, 535 536 /** 537 * The width that this dialogue should be resized to fullscreen. 538 * 539 * @attribute responsiveWidth 540 * @type Number 541 * @default 768 542 */ 543 responsiveWidth : { 544 value : 768 545 }, 546 547 /** 548 * Selector to a node that should recieve focus when this dialogue is shown. 549 * 550 * The default behaviour is to focus on the header. 551 * 552 * @attribute focusOnShowSelector 553 * @default null 554 * @type String 555 */ 556 focusOnShowSelector: { 557 value: null 558 } 559 560 } 561 }); 562 563 Y.Base.modifyAttrs(DIALOGUE, { 564 /** 565 * String with units, or number, representing the width of the Widget. 566 * If a number is provided, the default unit, defined by the Widgets 567 * DEF_UNIT, property is used. 568 * 569 * If a value of 'auto' is used, then an empty String is instead 570 * returned. 571 * 572 * @attribute width 573 * @default '400px' 574 * @type {String|Number} 575 */ 576 width: { 577 value: '400px', 578 setter: function(value) { 579 if (value === 'auto') { 580 return ''; 581 } 582 return value; 583 } 584 }, 585 586 /** 587 * Boolean indicating whether or not the Widget is visible. 588 * 589 * We override this from the default Widget attribute value. 590 * 591 * @attribute visible 592 * @default false 593 * @type Boolean 594 */ 595 visible: { 596 value: false 597 }, 598 599 /** 600 * A convenience Attribute, which can be used as a shortcut for the 601 * `align` Attribute. 602 * 603 * Note: We override this in Moodle such that it sets a value for the 604 * `center` attribute if set. The `centered` will always return false. 605 * 606 * @attribute centered 607 * @type Boolean|Node 608 * @default false 609 */ 610 centered: { 611 setter: function(value) { 612 if (value) { 613 this.set('center', true); 614 } 615 return false; 616 } 617 }, 618 619 /** 620 * Boolean determining whether to render the widget during initialisation. 621 * 622 * We override this to change the default from false to true for the dialogue. 623 * We then proceed to early render the dialogue during our initialisation rather than waiting 624 * for YUI to render it after that. 625 * 626 * @attribute render 627 * @type Boolean 628 * @default true 629 */ 630 render : { 631 value : true, 632 writeOnce : true 633 }, 634 635 /** 636 * Any additional classes to add to the boundingBox. 637 * 638 * @attribute extraClasses 639 * @type Array 640 * @default [] 641 */ 642 extraClasses: { 643 value: [] 644 } 645 }); 646 647 Y.Base.mix(DIALOGUE, [Y.M.core.WidgetFocusAfterHide]); 648 649 M.core.dialogue = DIALOGUE; 650 /** 651 * A dialogue type designed to display informative messages to users. 652 * 653 * @module moodle-core-notification 654 */ 655 656 /** 657 * Extends core Dialogue to provide a type of dialogue which can be used 658 * for informative message which are modal, and centered. 659 * 660 * @param {Object} config Object literal specifying the dialogue configuration properties. 661 * @constructor 662 * @class M.core.notification.info 663 * @extends M.core.dialogue 664 */ 665 var INFO = function() { 666 INFO.superclass.constructor.apply(this, arguments); 667 }; 668 669 Y.extend(INFO, M.core.dialogue, { 670 }, { 671 NAME: 'Moodle information dialogue', 672 CSS_PREFIX: DIALOGUE_PREFIX 673 }); 674 675 Y.Base.modifyAttrs(INFO, { 676 /** 677 * Boolean indicating whether or not the Widget is visible. 678 * 679 * We override this from the default M.core.dialogue attribute value. 680 * 681 * @attribute visible 682 * @default true 683 * @type Boolean 684 */ 685 visible: { 686 value: true 687 }, 688 689 /** 690 * Whether the widget should be modal or not. 691 * 692 * We override this to change the default from false to true for a subset of dialogues. 693 * 694 * @attribute modal 695 * @type Boolean 696 * @default true 697 */ 698 modal: { 699 validator: Y.Lang.isBoolean, 700 value: true 701 } 702 }); 703 704 M.core.notification = M.core.notification || {}; 705 M.core.notification.info = INFO; 706 707 708 }, '@VERSION@', { 709 "requires": [ 710 "base", 711 "node", 712 "panel", 713 "escape", 714 "event-key", 715 "dd-plugin", 716 "moodle-core-widget-focusafterclose", 717 "moodle-core-lockscroll" 718 ] 719 });
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 |