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