[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('widget-modality', function (Y, NAME) { 9 10 /** 11 * Provides modality support for Widgets, though an extension 12 * 13 * @module widget-modality 14 */ 15 16 var WIDGET = 'widget', 17 RENDER_UI = 'renderUI', 18 BIND_UI = 'bindUI', 19 SYNC_UI = 'syncUI', 20 BOUNDING_BOX = 'boundingBox', 21 VISIBLE = 'visible', 22 Z_INDEX = 'zIndex', 23 CHANGE = 'Change', 24 isBoolean = Y.Lang.isBoolean, 25 getCN = Y.ClassNameManager.getClassName, 26 MaskShow = "maskShow", 27 MaskHide = "maskHide", 28 ClickOutside = "clickoutside", 29 FocusOutside = "focusoutside", 30 31 supportsPosFixed = (function(){ 32 33 /*! IS_POSITION_FIXED_SUPPORTED - Juriy Zaytsev (kangax) - http://yura.thinkweb2.com/cft/ */ 34 35 var doc = Y.config.doc, 36 isSupported = null, 37 el, root; 38 39 if (doc.createElement) { 40 el = doc.createElement('div'); 41 if (el && el.style) { 42 el.style.position = 'fixed'; 43 el.style.top = '10px'; 44 root = doc.body; 45 if (root && root.appendChild && root.removeChild) { 46 root.appendChild(el); 47 isSupported = (el.offsetTop === 10); 48 root.removeChild(el); 49 } 50 } 51 } 52 53 return isSupported; 54 }()); 55 56 /** 57 * Widget extension, which can be used to add modality support to the base Widget class, 58 * through the Base.create method. 59 * 60 * @class WidgetModality 61 * @param {Object} config User configuration object 62 */ 63 function WidgetModal(config) {} 64 65 var MODAL = 'modal', 66 MASK = 'mask', 67 MODAL_CLASSES = { 68 modal : getCN(WIDGET, MODAL), 69 mask : getCN(WIDGET, MASK) 70 }; 71 72 /** 73 * Static property used to define the default attribute 74 * configuration introduced by WidgetModality. 75 * 76 * @property ATTRS 77 * @static 78 * @type Object 79 */ 80 WidgetModal.ATTRS = { 81 /** 82 * @attribute maskNode 83 * @type Y.Node 84 * 85 * @description Returns a Y.Node instance of the node being used as the mask. 86 */ 87 maskNode : { 88 getter : '_getMaskNode', 89 readOnly : true 90 }, 91 92 93 /** 94 * @attribute modal 95 * @type boolean 96 * 97 * @description Whether the widget should be modal or not. 98 */ 99 modal: { 100 value:false, 101 validator: isBoolean 102 }, 103 104 /** 105 * @attribute focusOn 106 * @type array 107 * 108 * @description An array of objects corresponding to the nodes and events that will trigger a re-focus back on the widget. 109 * The implementer can supply an array of objects, with each object having the following properties: 110 * <p>eventName: (string, required): The eventName to listen to.</p> 111 * <p>node: (Y.Node, optional): The Y.Node that will fire the event (defaults to the boundingBox of the widget)</p> 112 * <p>By default, this attribute consists of two objects which will cause the widget to re-focus if anything 113 * outside the widget is clicked on or focussed upon.</p> 114 */ 115 focusOn: { 116 valueFn: function() { 117 return [ 118 { 119 // node: this.get(BOUNDING_BOX), 120 eventName: ClickOutside 121 }, 122 { 123 //node: this.get(BOUNDING_BOX), 124 eventName: FocusOutside 125 } 126 ]; 127 }, 128 129 validator: Y.Lang.isArray 130 } 131 132 }; 133 134 135 WidgetModal.CLASSES = MODAL_CLASSES; 136 137 138 WidgetModal._MASK = null; 139 /** 140 * Returns the mask if it exists on the page - otherwise creates a mask. There's only 141 * one mask on a page at a given time. 142 * <p> 143 * This method in invoked internally by the getter of the maskNode ATTR. 144 * </p> 145 * @method _GET_MASK 146 * @static 147 */ 148 WidgetModal._GET_MASK = function() { 149 150 var mask = WidgetModal._MASK, 151 win = Y.one('win'); 152 153 if (mask && (mask.getDOMNode() !== null) && mask.inDoc()) { 154 return mask; 155 } 156 157 mask = Y.Node.create('<div></div>').addClass(MODAL_CLASSES.mask); 158 WidgetModal._MASK = mask; 159 160 if (supportsPosFixed) { 161 mask.setStyles({ 162 position: 'fixed', 163 width : '100%', 164 height : '100%', 165 top : '0', 166 left : '0', 167 display : 'block' 168 }); 169 } else { 170 mask.setStyles({ 171 position: 'absolute', 172 width : win.get('winWidth') +'px', 173 height : win.get('winHeight') + 'px', 174 top : '0', 175 left : '0', 176 display : 'block' 177 }); 178 } 179 180 return mask; 181 }; 182 183 /** 184 * A stack of Y.Widget objects representing the current hierarchy of modal widgets presently displayed on the screen 185 * @property STACK 186 */ 187 WidgetModal.STACK = []; 188 189 190 WidgetModal.prototype = { 191 192 initializer: function () { 193 Y.after(this._renderUIModal, this, RENDER_UI); 194 Y.after(this._syncUIModal, this, SYNC_UI); 195 Y.after(this._bindUIModal, this, BIND_UI); 196 }, 197 198 destructor: function () { 199 // Hack to remove this thing from the STACK. 200 this._uiSetHostVisibleModal(false); 201 }, 202 203 // *** Instance Members *** // 204 205 _uiHandlesModal: null, 206 207 208 /** 209 * Adds modal class to the bounding box of the widget 210 * <p> 211 * This method in invoked after renderUI is invoked for the Widget class 212 * using YUI's aop infrastructure. 213 * </p> 214 * @method _renderUIModal 215 * @protected 216 */ 217 _renderUIModal : function () { 218 219 var bb = this.get(BOUNDING_BOX); 220 //cb = this.get(CONTENT_BOX); 221 222 //this makes the content box content appear over the mask 223 // cb.setStyles({ 224 // position: "" 225 // }); 226 227 this._repositionMask(this); 228 bb.addClass(MODAL_CLASSES.modal); 229 230 }, 231 232 233 /** 234 * Hooks up methods to be executed when the widget's visibility or z-index changes 235 * <p> 236 * This method in invoked after bindUI is invoked for the Widget class 237 * using YUI's aop infrastructure. 238 * </p> 239 * @method _bindUIModal 240 * @protected 241 */ 242 _bindUIModal : function () { 243 244 this.after(VISIBLE+CHANGE, this._afterHostVisibleChangeModal); 245 this.after(Z_INDEX+CHANGE, this._afterHostZIndexChangeModal); 246 this.after("focusOnChange", this._afterFocusOnChange); 247 248 // Re-align the mask in the viewport if `position: fixed;` is not 249 // supported. iOS < 5 and Android < 3 don't actually support it even 250 // though they both pass the feature test; the UA sniff is here to 251 // account for that. Ideally this should be replaced with a better 252 // feature test. 253 if (!supportsPosFixed || 254 (Y.UA.ios && Y.UA.ios < 5) || 255 (Y.UA.android && Y.UA.android < 3)) { 256 257 Y.one('win').on('scroll', this._resyncMask, this); 258 } 259 }, 260 261 /** 262 * Syncs the mask with the widget's current state, namely the visibility and z-index of the widget 263 * <p> 264 * This method in invoked after syncUI is invoked for the Widget class 265 * using YUI's aop infrastructure. 266 * </p> 267 * @method _syncUIModal 268 * @protected 269 */ 270 _syncUIModal : function () { 271 272 //var host = this.get(HOST); 273 274 this._uiSetHostVisibleModal(this.get(VISIBLE)); 275 276 }, 277 278 /** 279 * Provides mouse and tab focus to the widget's bounding box. 280 * 281 * @method _focus 282 */ 283 _focus : function () { 284 285 var bb = this.get(BOUNDING_BOX), 286 oldTI = bb.get('tabIndex'); 287 288 bb.set('tabIndex', oldTI >= 0 ? oldTI : 0); 289 this.focus(); 290 }, 291 /** 292 * Blurs the widget. 293 * 294 * @method _blur 295 */ 296 _blur : function () { 297 298 this.blur(); 299 }, 300 301 /** 302 * Returns the Y.Node instance of the maskNode 303 * 304 * @method _getMaskNode 305 * @return {Node} The Y.Node instance of the mask, as returned from WidgetModal._GET_MASK 306 */ 307 _getMaskNode : function () { 308 309 return WidgetModal._GET_MASK(); 310 }, 311 312 /** 313 * Performs events attaching/detaching, stack shifting and mask repositioning based on the visibility of the widget 314 * 315 * @method _uiSetHostVisibleModal 316 * @param {boolean} Whether the widget is visible or not 317 */ 318 _uiSetHostVisibleModal : function (visible) { 319 var stack = WidgetModal.STACK, 320 maskNode = this.get('maskNode'), 321 isModal = this.get('modal'), 322 topModal, index; 323 324 if (visible) { 325 326 Y.Array.each(stack, function(modal){ 327 modal._detachUIHandlesModal(); 328 modal._blur(); 329 }); 330 331 // push on top of stack 332 stack.unshift(this); 333 334 this._repositionMask(this); 335 this._uiSetHostZIndexModal(this.get(Z_INDEX)); 336 337 if (isModal) { 338 maskNode.show(); 339 Y.later(1, this, '_attachUIHandlesModal'); 340 this._focus(); 341 } 342 343 344 } else { 345 346 index = Y.Array.indexOf(stack, this); 347 if (index >= 0) { 348 // Remove modal widget from global stack. 349 stack.splice(index, 1); 350 } 351 352 this._detachUIHandlesModal(); 353 this._blur(); 354 355 if (stack.length) { 356 topModal = stack[0]; 357 this._repositionMask(topModal); 358 //topModal._attachUIHandlesModal(); 359 topModal._uiSetHostZIndexModal(topModal.get(Z_INDEX)); 360 361 if (topModal.get('modal')) { 362 //topModal._attachUIHandlesModal(); 363 Y.later(1, topModal, '_attachUIHandlesModal'); 364 topModal._focus(); 365 } 366 367 } else { 368 369 if (maskNode.getStyle('display') === 'block') { 370 maskNode.hide(); 371 } 372 373 } 374 375 } 376 }, 377 378 /** 379 * Sets the z-index of the mask node. 380 * 381 * @method _uiSetHostZIndexModal 382 * @param {Number} Z-Index of the widget 383 */ 384 _uiSetHostZIndexModal : function (zIndex) { 385 386 if (this.get('modal')) { 387 this.get('maskNode').setStyle(Z_INDEX, zIndex || 0); 388 } 389 390 }, 391 392 /** 393 * Attaches UI Listeners for "clickoutside" and "focusoutside" on the 394 * widget. When these events occur, and the widget is modal, focus is 395 * shifted back onto the widget. 396 * 397 * @method _attachUIHandlesModal 398 */ 399 _attachUIHandlesModal : function () { 400 401 if (this._uiHandlesModal || WidgetModal.STACK[0] !== this) { 402 // Quit early if we have ui handles, or if we not at the top 403 // of the global stack. 404 return; 405 } 406 407 var bb = this.get(BOUNDING_BOX), 408 maskNode = this.get('maskNode'), 409 focusOn = this.get('focusOn'), 410 focus = Y.bind(this._focus, this), 411 uiHandles = [], 412 i, len, o; 413 414 for (i = 0, len = focusOn.length; i < len; i++) { 415 416 o = {}; 417 o.node = focusOn[i].node; 418 o.ev = focusOn[i].eventName; 419 o.keyCode = focusOn[i].keyCode; 420 421 //no keycode or node defined 422 if (!o.node && !o.keyCode && o.ev) { 423 uiHandles.push(bb.on(o.ev, focus)); 424 } 425 426 //node defined, no keycode (not a keypress) 427 else if (o.node && !o.keyCode && o.ev) { 428 uiHandles.push(o.node.on(o.ev, focus)); 429 } 430 431 //node defined, keycode defined, event defined (its a key press) 432 else if (o.node && o.keyCode && o.ev) { 433 uiHandles.push(o.node.on(o.ev, focus, o.keyCode)); 434 } 435 436 else { 437 Y.Log('focusOn ATTR Error: The event with name "'+o.ev+'" could not be attached.'); 438 } 439 440 } 441 442 if ( ! supportsPosFixed) { 443 uiHandles.push(Y.one('win').on('scroll', Y.bind(function(){ 444 maskNode.setStyle('top', maskNode.get('docScrollY')); 445 }, this))); 446 } 447 448 this._uiHandlesModal = uiHandles; 449 }, 450 451 /** 452 * Detaches all UI Listeners that were set in _attachUIHandlesModal from the widget. 453 * 454 * @method _detachUIHandlesModal 455 */ 456 _detachUIHandlesModal : function () { 457 Y.each(this._uiHandlesModal, function(h){ 458 h.detach(); 459 }); 460 this._uiHandlesModal = null; 461 }, 462 463 /** 464 * Default function that is called when visibility is changed on the widget. 465 * 466 * @method _afterHostVisibleChangeModal 467 * @param {EventFacade} e The event facade of the change 468 */ 469 _afterHostVisibleChangeModal : function (e) { 470 471 this._uiSetHostVisibleModal(e.newVal); 472 }, 473 474 /** 475 * Default function that is called when z-index is changed on the widget. 476 * 477 * @method _afterHostZIndexChangeModal 478 * @param {EventFacade} e The event facade of the change 479 */ 480 _afterHostZIndexChangeModal : function (e) { 481 482 this._uiSetHostZIndexModal(e.newVal); 483 }, 484 485 /** 486 * Returns a boolean representing whether the current widget is in a "nested modality" state. 487 * This is done by checking the number of widgets currently on the stack. 488 * 489 * @method isNested 490 * @public 491 */ 492 isNested: function() { 493 var length = WidgetModal.STACK.length, 494 retval = (length > 1) ? true : false; 495 return retval; 496 }, 497 498 /** 499 * Repositions the mask in the DOM for nested modality cases. 500 * 501 * @method _repositionMask 502 * @param {Widget} nextElem The Y.Widget instance that will be visible in the stack once the current widget is closed. 503 */ 504 _repositionMask: function(nextElem) { 505 506 var currentModal = this.get('modal'), 507 nextModal = nextElem.get('modal'), 508 maskNode = this.get('maskNode'), 509 bb, bbParent; 510 511 //if this is modal and host is not modal 512 if (currentModal && !nextModal) { 513 //leave the mask where it is, since the host is not modal. 514 maskNode.remove(); 515 this.fire(MaskHide); 516 } 517 518 //if the main widget is not modal but the host is modal, or both of them are modal 519 else if ((!currentModal && nextModal) || (currentModal && nextModal)) { 520 521 //then remove the mask off DOM, reposition it, and reinsert it into the DOM 522 maskNode.remove(); 523 this.fire(MaskHide); 524 bb = nextElem.get(BOUNDING_BOX); 525 bbParent = bb.get('parentNode') || Y.one('body'); 526 bbParent.insert(maskNode, bbParent.get('firstChild')); 527 this.fire(MaskShow); 528 } 529 530 }, 531 532 /** 533 * Resyncs the mask in the viewport for browsers that don't support fixed positioning 534 * 535 * @method _resyncMask 536 * @param {Y.Widget} nextElem The Y.Widget instance that will be visible in the stack once the current widget is closed. 537 * @private 538 */ 539 _resyncMask: function (e) { 540 var o = e.currentTarget, 541 offsetX = o.get('docScrollX'), 542 offsetY = o.get('docScrollY'), 543 w = o.get('innerWidth') || o.get('winWidth'), 544 h = o.get('innerHeight') || o.get('winHeight'), 545 mask = this.get('maskNode'); 546 547 mask.setStyles({ 548 "top": offsetY + "px", 549 "left": offsetX + "px", 550 "width": w + 'px', 551 "height": h + 'px' 552 }); 553 }, 554 555 /** 556 * Default function called when focusOn Attribute is changed. Remove existing listeners and create new listeners. 557 * 558 * @method _afterFocusOnChange 559 */ 560 _afterFocusOnChange : function() { 561 this._detachUIHandlesModal(); 562 563 if (this.get(VISIBLE)) { 564 this._attachUIHandlesModal(); 565 } 566 } 567 }; 568 569 Y.WidgetModality = WidgetModal; 570 571 572 573 }, '3.17.2', {"requires": ["base-build", "event-outside", "widget"], "skinnable": true});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |