[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/widget-modality/ -> widget-modality.js (source)

   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});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1