diff -r 322d0feea350 -r 89ef5ed3c48b src/cm/media/js/lib/yui/yui_3.10.3/build/widget-modality/widget-modality.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui_3.10.3/build/widget-modality/widget-modality.js Tue Jul 16 14:29:46 2013 +0200 @@ -0,0 +1,572 @@ +/* +YUI 3.10.3 (build 2fb5187) +Copyright 2013 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +http://yuilibrary.com/license/ +*/ + +YUI.add('widget-modality', function (Y, NAME) { + +/** + * Provides modality support for Widgets, though an extension + * + * @module widget-modality + */ + +var WIDGET = 'widget', + RENDER_UI = 'renderUI', + BIND_UI = 'bindUI', + SYNC_UI = 'syncUI', + BOUNDING_BOX = 'boundingBox', + CONTENT_BOX = 'contentBox', + VISIBLE = 'visible', + Z_INDEX = 'zIndex', + CHANGE = 'Change', + isBoolean = Y.Lang.isBoolean, + getCN = Y.ClassNameManager.getClassName, + MaskShow = "maskShow", + MaskHide = "maskHide", + ClickOutside = "clickoutside", + FocusOutside = "focusoutside", + + supportsPosFixed = (function(){ + + /*! IS_POSITION_FIXED_SUPPORTED - Juriy Zaytsev (kangax) - http://yura.thinkweb2.com/cft/ */ + + var doc = Y.config.doc, + isSupported = null, + el, root; + + if (doc.createElement) { + el = doc.createElement('div'); + if (el && el.style) { + el.style.position = 'fixed'; + el.style.top = '10px'; + root = doc.body; + if (root && root.appendChild && root.removeChild) { + root.appendChild(el); + isSupported = (el.offsetTop === 10); + root.removeChild(el); + } + } + } + + return isSupported; + }()); + + /** + * Widget extension, which can be used to add modality support to the base Widget class, + * through the Base.create method. + * + * @class WidgetModality + * @param {Object} config User configuration object + */ + function WidgetModal(config) {} + + var MODAL = 'modal', + MASK = 'mask', + MODAL_CLASSES = { + modal : getCN(WIDGET, MODAL), + mask : getCN(WIDGET, MASK) + }; + + /** + * Static property used to define the default attribute + * configuration introduced by WidgetModality. + * + * @property ATTRS + * @static + * @type Object + */ + WidgetModal.ATTRS = { + /** + * @attribute maskNode + * @type Y.Node + * + * @description Returns a Y.Node instance of the node being used as the mask. + */ + maskNode : { + getter : '_getMaskNode', + readOnly : true + }, + + + /** + * @attribute modal + * @type boolean + * + * @description Whether the widget should be modal or not. + */ + modal: { + value:false, + validator: isBoolean + }, + + /** + * @attribute focusOn + * @type array + * + * @description An array of objects corresponding to the nodes and events that will trigger a re-focus back on the widget. + * The implementer can supply an array of objects, with each object having the following properties: + *

eventName: (string, required): The eventName to listen to.

+ *

node: (Y.Node, optional): The Y.Node that will fire the event (defaults to the boundingBox of the widget)

+ *

By default, this attribute consists of two objects which will cause the widget to re-focus if anything + * outside the widget is clicked on or focussed upon.

+ */ + focusOn: { + valueFn: function() { + return [ + { + // node: this.get(BOUNDING_BOX), + eventName: ClickOutside + }, + { + //node: this.get(BOUNDING_BOX), + eventName: FocusOutside + } + ]; + }, + + validator: Y.Lang.isArray + } + + }; + + + WidgetModal.CLASSES = MODAL_CLASSES; + + + /** + * Returns the mask if it exists on the page - otherwise creates a mask. There's only + * one mask on a page at a given time. + *

+ * This method in invoked internally by the getter of the maskNode ATTR. + *

+ * @method _GET_MASK + * @static + */ + WidgetModal._GET_MASK = function() { + + var mask = Y.one('.' + MODAL_CLASSES.mask), + win = Y.one('win'); + + if (mask) { + return mask; + } + + mask = Y.Node.create('
').addClass(MODAL_CLASSES.mask); + + if (supportsPosFixed) { + mask.setStyles({ + position: 'fixed', + width : '100%', + height : '100%', + top : '0', + left : '0', + display : 'block' + }); + } else { + mask.setStyles({ + position: 'absolute', + width : win.get('winWidth') +'px', + height : win.get('winHeight') + 'px', + top : '0', + left : '0', + display : 'block' + }); + } + + return mask; + }; + + /** + * A stack of Y.Widget objects representing the current hierarchy of modal widgets presently displayed on the screen + * @property STACK + */ + WidgetModal.STACK = []; + + + WidgetModal.prototype = { + + initializer: function () { + Y.after(this._renderUIModal, this, RENDER_UI); + Y.after(this._syncUIModal, this, SYNC_UI); + Y.after(this._bindUIModal, this, BIND_UI); + }, + + destructor: function () { + // Hack to remove this thing from the STACK. + this._uiSetHostVisibleModal(false); + }, + + // *** Instance Members *** // + + _uiHandlesModal: null, + + + /** + * Adds modal class to the bounding box of the widget + *

+ * This method in invoked after renderUI is invoked for the Widget class + * using YUI's aop infrastructure. + *

+ * @method _renderUIModal + * @protected + */ + _renderUIModal : function () { + + var bb = this.get(BOUNDING_BOX); + //cb = this.get(CONTENT_BOX); + + //this makes the content box content appear over the mask + // cb.setStyles({ + // position: "" + // }); + + this._repositionMask(this); + bb.addClass(MODAL_CLASSES.modal); + + }, + + + /** + * Hooks up methods to be executed when the widget's visibility or z-index changes + *

+ * This method in invoked after bindUI is invoked for the Widget class + * using YUI's aop infrastructure. + *

+ * @method _bindUIModal + * @protected + */ + _bindUIModal : function () { + + this.after(VISIBLE+CHANGE, this._afterHostVisibleChangeModal); + this.after(Z_INDEX+CHANGE, this._afterHostZIndexChangeModal); + this.after("focusOnChange", this._afterFocusOnChange); + + // Re-align the mask in the viewport if `position: fixed;` is not + // supported. iOS < 5 and Android < 3 don't actually support it even + // though they both pass the feature test; the UA sniff is here to + // account for that. Ideally this should be replaced with a better + // feature test. + if (!supportsPosFixed || + (Y.UA.ios && Y.UA.ios < 5) || + (Y.UA.android && Y.UA.android < 3)) { + + Y.one('win').on('scroll', this._resyncMask, this); + } + }, + + /** + * Syncs the mask with the widget's current state, namely the visibility and z-index of the widget + *

+ * This method in invoked after syncUI is invoked for the Widget class + * using YUI's aop infrastructure. + *

+ * @method _syncUIModal + * @protected + */ + _syncUIModal : function () { + + //var host = this.get(HOST); + + this._uiSetHostVisibleModal(this.get(VISIBLE)); + + }, + + /** + * Provides mouse and tab focus to the widget's bounding box. + * + * @method _focus + */ + _focus : function (e) { + + var bb = this.get(BOUNDING_BOX), + oldTI = bb.get('tabIndex'); + + bb.set('tabIndex', oldTI >= 0 ? oldTI : 0); + this.focus(); + }, + /** + * Blurs the widget. + * + * @method _blur + */ + _blur : function () { + + this.blur(); + }, + + /** + * Returns the Y.Node instance of the maskNode + * + * @method _getMaskNode + * @return {Node} The Y.Node instance of the mask, as returned from WidgetModal._GET_MASK + */ + _getMaskNode : function () { + + return WidgetModal._GET_MASK(); + }, + + /** + * Performs events attaching/detaching, stack shifting and mask repositioning based on the visibility of the widget + * + * @method _uiSetHostVisibleModal + * @param {boolean} Whether the widget is visible or not + */ + _uiSetHostVisibleModal : function (visible) { + var stack = WidgetModal.STACK, + maskNode = this.get('maskNode'), + isModal = this.get('modal'), + topModal, index; + + if (visible) { + + Y.Array.each(stack, function(modal){ + modal._detachUIHandlesModal(); + modal._blur(); + }); + + // push on top of stack + stack.unshift(this); + + this._repositionMask(this); + this._uiSetHostZIndexModal(this.get(Z_INDEX)); + + if (isModal) { + maskNode.show(); + Y.later(1, this, '_attachUIHandlesModal'); + this._focus(); + } + + + } else { + + index = Y.Array.indexOf(stack, this); + if (index >= 0) { + // Remove modal widget from global stack. + stack.splice(index, 1); + } + + this._detachUIHandlesModal(); + this._blur(); + + if (stack.length) { + topModal = stack[0]; + this._repositionMask(topModal); + //topModal._attachUIHandlesModal(); + topModal._uiSetHostZIndexModal(topModal.get(Z_INDEX)); + + if (topModal.get('modal')) { + //topModal._attachUIHandlesModal(); + Y.later(1, topModal, '_attachUIHandlesModal'); + topModal._focus(); + } + + } else { + + if (maskNode.getStyle('display') === 'block') { + maskNode.hide(); + } + + } + + } + }, + + /** + * Sets the z-index of the mask node. + * + * @method _uiSetHostZIndexModal + * @param {Number} Z-Index of the widget + */ + _uiSetHostZIndexModal : function (zIndex) { + + if (this.get('modal')) { + this.get('maskNode').setStyle(Z_INDEX, zIndex || 0); + } + + }, + + /** + * Attaches UI Listeners for "clickoutside" and "focusoutside" on the + * widget. When these events occur, and the widget is modal, focus is + * shifted back onto the widget. + * + * @method _attachUIHandlesModal + */ + _attachUIHandlesModal : function () { + + if (this._uiHandlesModal || WidgetModal.STACK[0] !== this) { + // Quit early if we have ui handles, or if we not at the top + // of the global stack. + return; + } + + var bb = this.get(BOUNDING_BOX), + maskNode = this.get('maskNode'), + focusOn = this.get('focusOn'), + focus = Y.bind(this._focus, this), + uiHandles = [], + i, len, o; + + for (i = 0, len = focusOn.length; i < len; i++) { + + o = {}; + o.node = focusOn[i].node; + o.ev = focusOn[i].eventName; + o.keyCode = focusOn[i].keyCode; + + //no keycode or node defined + if (!o.node && !o.keyCode && o.ev) { + uiHandles.push(bb.on(o.ev, focus)); + } + + //node defined, no keycode (not a keypress) + else if (o.node && !o.keyCode && o.ev) { + uiHandles.push(o.node.on(o.ev, focus)); + } + + //node defined, keycode defined, event defined (its a key press) + else if (o.node && o.keyCode && o.ev) { + uiHandles.push(o.node.on(o.ev, focus, o.keyCode)); + } + + else { + Y.Log('focusOn ATTR Error: The event with name "'+o.ev+'" could not be attached.'); + } + + } + + if ( ! supportsPosFixed) { + uiHandles.push(Y.one('win').on('scroll', Y.bind(function(e){ + maskNode.setStyle('top', maskNode.get('docScrollY')); + }, this))); + } + + this._uiHandlesModal = uiHandles; + }, + + /** + * Detaches all UI Listeners that were set in _attachUIHandlesModal from the widget. + * + * @method _detachUIHandlesModal + */ + _detachUIHandlesModal : function () { + Y.each(this._uiHandlesModal, function(h){ + h.detach(); + }); + this._uiHandlesModal = null; + }, + + /** + * Default function that is called when visibility is changed on the widget. + * + * @method _afterHostVisibleChangeModal + * @param {EventFacade} e The event facade of the change + */ + _afterHostVisibleChangeModal : function (e) { + + this._uiSetHostVisibleModal(e.newVal); + }, + + /** + * Default function that is called when z-index is changed on the widget. + * + * @method _afterHostZIndexChangeModal + * @param {EventFacade} e The event facade of the change + */ + _afterHostZIndexChangeModal : function (e) { + + this._uiSetHostZIndexModal(e.newVal); + }, + + /** + * Returns a boolean representing whether the current widget is in a "nested modality" state. + * This is done by checking the number of widgets currently on the stack. + * + * @method isNested + * @public + */ + isNested: function() { + var length = WidgetModal.STACK.length, + retval = (length > 1) ? true : false; + return retval; + }, + + /** + * Repositions the mask in the DOM for nested modality cases. + * + * @method _repositionMask + * @param {Widget} nextElem The Y.Widget instance that will be visible in the stack once the current widget is closed. + */ + _repositionMask: function(nextElem) { + + var currentModal = this.get('modal'), + nextModal = nextElem.get('modal'), + maskNode = this.get('maskNode'), + bb, bbParent; + + //if this is modal and host is not modal + if (currentModal && !nextModal) { + //leave the mask where it is, since the host is not modal. + maskNode.remove(); + this.fire(MaskHide); + } + + //if the main widget is not modal but the host is modal, or both of them are modal + else if ((!currentModal && nextModal) || (currentModal && nextModal)) { + + //then remove the mask off DOM, reposition it, and reinsert it into the DOM + maskNode.remove(); + this.fire(MaskHide); + bb = nextElem.get(BOUNDING_BOX); + bbParent = bb.get('parentNode') || Y.one('body'); + bbParent.insert(maskNode, bbParent.get('firstChild')); + this.fire(MaskShow); + } + + }, + + /** + * Resyncs the mask in the viewport for browsers that don't support fixed positioning + * + * @method _resyncMask + * @param {Y.Widget} nextElem The Y.Widget instance that will be visible in the stack once the current widget is closed. + * @private + */ + _resyncMask: function (e) { + var o = e.currentTarget, + offsetX = o.get('docScrollX'), + offsetY = o.get('docScrollY'), + w = o.get('innerWidth') || o.get('winWidth'), + h = o.get('innerHeight') || o.get('winHeight'), + mask = this.get('maskNode'); + + mask.setStyles({ + "top": offsetY + "px", + "left": offsetX + "px", + "width": w + 'px', + "height": h + 'px' + }); + }, + + /** + * Default function called when focusOn Attribute is changed. Remove existing listeners and create new listeners. + * + * @method _afterFocusOnChange + */ + _afterFocusOnChange : function(e) { + this._detachUIHandlesModal(); + + if (this.get(VISIBLE)) { + this._attachUIHandlesModal(); + } + } + }; + + Y.WidgetModality = WidgetModal; + + + +}, '3.10.3', {"requires": ["base-build", "event-outside", "widget"], "skinnable": true});