API Docs for: 3.8.0
Show:

File: widget-stack/js/Widget-Stack.js

  1. /**
  2.  * Provides stackable (z-index) support for Widgets through an extension.
  3.  *
  4.  * @module widget-stack
  5.  */
  6.     var L = Y.Lang,
  7.         UA = Y.UA,
  8.         Node = Y.Node,
  9.         Widget = Y.Widget,

  10.         ZINDEX = "zIndex",
  11.         SHIM = "shim",
  12.         VISIBLE = "visible",

  13.         BOUNDING_BOX = "boundingBox",

  14.         RENDER_UI = "renderUI",
  15.         BIND_UI = "bindUI",
  16.         SYNC_UI = "syncUI",

  17.         OFFSET_WIDTH = "offsetWidth",
  18.         OFFSET_HEIGHT = "offsetHeight",
  19.         PARENT_NODE = "parentNode",
  20.         FIRST_CHILD = "firstChild",
  21.         OWNER_DOCUMENT = "ownerDocument",

  22.         WIDTH = "width",
  23.         HEIGHT = "height",
  24.         PX = "px",

  25.         // HANDLE KEYS
  26.         SHIM_DEFERRED = "shimdeferred",
  27.         SHIM_RESIZE = "shimresize",

  28.         // Events
  29.         VisibleChange = "visibleChange",
  30.         WidthChange = "widthChange",
  31.         HeightChange = "heightChange",
  32.         ShimChange = "shimChange",
  33.         ZIndexChange = "zIndexChange",
  34.         ContentUpdate = "contentUpdate",

  35.         // CSS
  36.         STACKED = "stacked";

  37.     /**
  38.      * Widget extension, which can be used to add stackable (z-index) support to the
  39.      * base Widget class along with a shimming solution, through the
  40.      * <a href="Base.html#method_build">Base.build</a> method.
  41.      *
  42.      * @class WidgetStack
  43.      * @param {Object} User configuration object
  44.      */
  45.     function Stack(config) {
  46.         this._stackNode = this.get(BOUNDING_BOX);
  47.         this._stackHandles = {};

  48.         // WIDGET METHOD OVERLAP
  49.         Y.after(this._renderUIStack, this, RENDER_UI);
  50.         Y.after(this._syncUIStack, this, SYNC_UI);
  51.         Y.after(this._bindUIStack, this, BIND_UI);
  52.     }

  53.     // Static Properties
  54.     /**
  55.      * Static property used to define the default attribute
  56.      * configuration introduced by WidgetStack.
  57.      *
  58.      * @property ATTRS
  59.      * @type Object
  60.      * @static
  61.      */
  62.     Stack.ATTRS = {
  63.         /**
  64.          * @attribute shim
  65.          * @type boolean
  66.          * @default false, for all browsers other than IE6, for which a shim is enabled by default.
  67.          *
  68.          * @description Boolean flag to indicate whether or not a shim should be added to the Widgets
  69.          * boundingBox, to protect it from select box bleedthrough.
  70.          */
  71.         shim: {
  72.             value: (UA.ie == 6)
  73.         },

  74.         /**
  75.          * @attribute zIndex
  76.          * @type number
  77.          * @default 0
  78.          * @description The z-index to apply to the Widgets boundingBox. Non-numerical values for
  79.          * zIndex will be converted to 0
  80.          */
  81.         zIndex: {
  82.             value : 0,
  83.             setter: '_setZIndex'
  84.         }
  85.     };

  86.     /**
  87.      * The HTML parsing rules for the WidgetStack class.
  88.      *
  89.      * @property HTML_PARSER
  90.      * @static
  91.      * @type Object
  92.      */
  93.     Stack.HTML_PARSER = {
  94.         zIndex: function (srcNode) {
  95.             return this._parseZIndex(srcNode);
  96.         }
  97.     };

  98.     /**
  99.      * Default class used to mark the shim element
  100.      *
  101.      * @property SHIM_CLASS_NAME
  102.      * @type String
  103.      * @static
  104.      * @default "yui3-widget-shim"
  105.      */
  106.     Stack.SHIM_CLASS_NAME = Widget.getClassName(SHIM);

  107.     /**
  108.      * Default class used to mark the boundingBox of a stacked widget.
  109.      *
  110.      * @property STACKED_CLASS_NAME
  111.      * @type String
  112.      * @static
  113.      * @default "yui3-widget-stacked"
  114.      */
  115.     Stack.STACKED_CLASS_NAME = Widget.getClassName(STACKED);

  116.     /**
  117.      * Default markup template used to generate the shim element.
  118.      *
  119.      * @property SHIM_TEMPLATE
  120.      * @type String
  121.      * @static
  122.      */
  123.     Stack.SHIM_TEMPLATE = '<iframe class="' + Stack.SHIM_CLASS_NAME + '" frameborder="0" title="Widget Stacking Shim" src="javascript:false" tabindex="-1" role="presentation"></iframe>';

  124.     Stack.prototype = {

  125.         /**
  126.          * Synchronizes the UI to match the Widgets stack state. This method in
  127.          * invoked after syncUI is invoked for the Widget class using YUI's aop infrastructure.
  128.          *
  129.          * @method _syncUIStack
  130.          * @protected
  131.          */
  132.         _syncUIStack: function() {
  133.             this._uiSetShim(this.get(SHIM));
  134.             this._uiSetZIndex(this.get(ZINDEX));
  135.         },

  136.         /**
  137.          * Binds event listeners responsible for updating the UI state in response to
  138.          * Widget stack related state changes.
  139.          * <p>
  140.          * This method is invoked after bindUI is invoked for the Widget class
  141.          * using YUI's aop infrastructure.
  142.          * </p>
  143.          * @method _bindUIStack
  144.          * @protected
  145.          */
  146.         _bindUIStack: function() {
  147.             this.after(ShimChange, this._afterShimChange);
  148.             this.after(ZIndexChange, this._afterZIndexChange);
  149.         },

  150.         /**
  151.          * Creates/Initializes the DOM to support stackability.
  152.          * <p>
  153.          * This method in invoked after renderUI is invoked for the Widget class
  154.          * using YUI's aop infrastructure.
  155.          * </p>
  156.          * @method _renderUIStack
  157.          * @protected
  158.          */
  159.         _renderUIStack: function() {
  160.             this._stackNode.addClass(Stack.STACKED_CLASS_NAME);
  161.         },

  162.         /**
  163.         Parses a `zIndex` attribute value from this widget's `srcNode`.

  164.         @method _parseZIndex
  165.         @param {Node} srcNode The node to parse a `zIndex` value from.
  166.         @return {Mixed} The parsed `zIndex` value.
  167.         @protected
  168.         **/
  169.         _parseZIndex: function (srcNode) {
  170.             var zIndex;

  171.             // Prefers how WebKit handles `z-index` which better matches the
  172.             // spec:
  173.             //
  174.             // * http://www.w3.org/TR/CSS2/visuren.html#z-index
  175.             // * https://bugs.webkit.org/show_bug.cgi?id=15562
  176.             //
  177.             // When a node isn't rendered in the document, and/or when a
  178.             // node is not positioned, then it doesn't have a context to derive
  179.             // a valid `z-index` value from.
  180.             if (!srcNode.inDoc() || srcNode.getStyle('position') === 'static') {
  181.                 zIndex = 'auto';
  182.             } else {
  183.                 // Uses `getComputedStyle()` because it has greater accuracy in
  184.                 // more browsers than `getStyle()` does for `z-index`.
  185.                 zIndex = srcNode.getComputedStyle('zIndex');
  186.             }

  187.             // This extension adds a stacking context to widgets, therefore a
  188.             // `srcNode` witout a stacking context (i.e. "auto") will return
  189.             // `null` from this DOM parser. This way the widget's default or
  190.             // user provided value for `zIndex` will be used.
  191.             return zIndex === 'auto' ? null : zIndex;
  192.         },

  193.         /**
  194.          * Default setter for zIndex attribute changes. Normalizes zIndex values to
  195.          * numbers, converting non-numerical values to 0.
  196.          *
  197.          * @method _setZIndex
  198.          * @protected
  199.          * @param {String | Number} zIndex
  200.          * @return {Number} Normalized zIndex
  201.          */
  202.         _setZIndex: function(zIndex) {
  203.             if (L.isString(zIndex)) {
  204.                 zIndex = parseInt(zIndex, 10);
  205.             }
  206.             if (!L.isNumber(zIndex)) {
  207.                 zIndex = 0;
  208.             }
  209.             return zIndex;
  210.         },

  211.         /**
  212.          * Default attribute change listener for the shim attribute, responsible
  213.          * for updating the UI, in response to attribute changes.
  214.          *
  215.          * @method _afterShimChange
  216.          * @protected
  217.          * @param {EventFacade} e The event facade for the attribute change
  218.          */
  219.         _afterShimChange : function(e) {
  220.             this._uiSetShim(e.newVal);
  221.         },

  222.         /**
  223.          * Default attribute change listener for the zIndex attribute, responsible
  224.          * for updating the UI, in response to attribute changes.
  225.          *
  226.          * @method _afterZIndexChange
  227.          * @protected
  228.          * @param {EventFacade} e The event facade for the attribute change
  229.          */
  230.         _afterZIndexChange : function(e) {
  231.             this._uiSetZIndex(e.newVal);
  232.         },

  233.         /**
  234.          * Updates the UI to reflect the zIndex value passed in.
  235.          *
  236.          * @method _uiSetZIndex
  237.          * @protected
  238.          * @param {number} zIndex The zindex to be reflected in the UI
  239.          */
  240.         _uiSetZIndex: function (zIndex) {
  241.             this._stackNode.setStyle(ZINDEX, zIndex);
  242.         },

  243.         /**
  244.          * Updates the UI to enable/disable the shim. If the widget is not currently visible,
  245.          * creation of the shim is deferred until it is made visible, for performance reasons.
  246.          *
  247.          * @method _uiSetShim
  248.          * @protected
  249.          * @param {boolean} enable If true, creates/renders the shim, if false, removes it.
  250.          */
  251.         _uiSetShim: function (enable) {
  252.             if (enable) {
  253.                 // Lazy creation
  254.                 if (this.get(VISIBLE)) {
  255.                     this._renderShim();
  256.                 } else {
  257.                     this._renderShimDeferred();
  258.                 }

  259.                 // Eagerly attach resize handlers
  260.                 //
  261.                 // Required because of Event stack behavior, commit ref: cd8dddc
  262.                 // Should be revisted after Ticket #2531067 is resolved.
  263.                 if (UA.ie == 6) {
  264.                     this._addShimResizeHandlers();
  265.                 }
  266.             } else {
  267.                 this._destroyShim();
  268.             }
  269.         },

  270.         /**
  271.          * Sets up change handlers for the visible attribute, to defer shim creation/rendering
  272.          * until the Widget is made visible.
  273.          *
  274.          * @method _renderShimDeferred
  275.          * @private
  276.          */
  277.         _renderShimDeferred : function() {

  278.             this._stackHandles[SHIM_DEFERRED] = this._stackHandles[SHIM_DEFERRED] || [];

  279.             var handles = this._stackHandles[SHIM_DEFERRED],
  280.                 createBeforeVisible = function(e) {
  281.                     if (e.newVal) {
  282.                         this._renderShim();
  283.                     }
  284.                 };

  285.             handles.push(this.on(VisibleChange, createBeforeVisible));
  286.             // Depending how how Ticket #2531067 is resolved, a reversal of
  287.             // commit ref: cd8dddc could lead to a more elagent solution, with
  288.             // the addition of this line here:
  289.             //
  290.             // handles.push(this.after(VisibleChange, this.sizeShim));
  291.         },

  292.         /**
  293.          * Sets up event listeners to resize the shim when the size of the Widget changes.
  294.          * <p>
  295.          * NOTE: This method is only used for IE6 currently, since IE6 doesn't support a way to
  296.          * resize the shim purely through CSS, when the Widget does not have an explicit width/height
  297.          * set.
  298.          * </p>
  299.          * @method _addShimResizeHandlers
  300.          * @private
  301.          */
  302.         _addShimResizeHandlers : function() {

  303.             this._stackHandles[SHIM_RESIZE] = this._stackHandles[SHIM_RESIZE] || [];

  304.             var sizeShim = this.sizeShim,
  305.                 handles = this._stackHandles[SHIM_RESIZE];

  306.             handles.push(this.after(VisibleChange, sizeShim));
  307.             handles.push(this.after(WidthChange, sizeShim));
  308.             handles.push(this.after(HeightChange, sizeShim));
  309.             handles.push(this.after(ContentUpdate, sizeShim));
  310.         },

  311.         /**
  312.          * Detaches any handles stored for the provided key
  313.          *
  314.          * @method _detachStackHandles
  315.          * @param String handleKey The key defining the group of handles which should be detached
  316.          * @private
  317.          */
  318.         _detachStackHandles : function(handleKey) {
  319.             var handles = this._stackHandles[handleKey],
  320.                 handle;

  321.             if (handles && handles.length > 0) {
  322.                 while((handle = handles.pop())) {
  323.                     handle.detach();
  324.                 }
  325.             }
  326.         },

  327.         /**
  328.          * Creates the shim element and adds it to the DOM
  329.          *
  330.          * @method _renderShim
  331.          * @private
  332.          */
  333.         _renderShim : function() {
  334.             var shimEl = this._shimNode,
  335.                 stackEl = this._stackNode;

  336.             if (!shimEl) {
  337.                 shimEl = this._shimNode = this._getShimTemplate();
  338.                 stackEl.insertBefore(shimEl, stackEl.get(FIRST_CHILD));

  339.                 this._detachStackHandles(SHIM_DEFERRED);
  340.                 this.sizeShim();
  341.             }
  342.         },

  343.         /**
  344.          * Removes the shim from the DOM, and detaches any related event
  345.          * listeners.
  346.          *
  347.          * @method _destroyShim
  348.          * @private
  349.          */
  350.         _destroyShim : function() {
  351.             if (this._shimNode) {
  352.                 this._shimNode.get(PARENT_NODE).removeChild(this._shimNode);
  353.                 this._shimNode = null;

  354.                 this._detachStackHandles(SHIM_DEFERRED);
  355.                 this._detachStackHandles(SHIM_RESIZE);
  356.             }
  357.         },

  358.         /**
  359.          * For IE6, synchronizes the size and position of iframe shim to that of
  360.          * Widget bounding box which it is protecting. For all other browsers,
  361.          * this method does not do anything.
  362.          *
  363.          * @method sizeShim
  364.          */
  365.         sizeShim: function () {
  366.             var shim = this._shimNode,
  367.                 node = this._stackNode;

  368.             if (shim && UA.ie === 6 && this.get(VISIBLE)) {
  369.                 shim.setStyle(WIDTH, node.get(OFFSET_WIDTH) + PX);
  370.                 shim.setStyle(HEIGHT, node.get(OFFSET_HEIGHT) + PX);
  371.             }
  372.         },

  373.         /**
  374.          * Creates a cloned shim node, using the SHIM_TEMPLATE html template, for use on a new instance.
  375.          *
  376.          * @method _getShimTemplate
  377.          * @private
  378.          * @return {Node} node A new shim Node instance.
  379.          */
  380.         _getShimTemplate : function() {
  381.             return Node.create(Stack.SHIM_TEMPLATE, this._stackNode.get(OWNER_DOCUMENT));
  382.         }
  383.     };

  384.     Y.WidgetStack = Stack;

  385.