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