API Docs for: 3.17.2
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.  
  11. ZINDEX = "zIndex",
  12. SHIM = "shim",
  13. VISIBLE = "visible",
  14.  
  15. BOUNDING_BOX = "boundingBox",
  16.  
  17. RENDER_UI = "renderUI",
  18. BIND_UI = "bindUI",
  19. SYNC_UI = "syncUI",
  20.  
  21. OFFSET_WIDTH = "offsetWidth",
  22. OFFSET_HEIGHT = "offsetHeight",
  23. PARENT_NODE = "parentNode",
  24. FIRST_CHILD = "firstChild",
  25. OWNER_DOCUMENT = "ownerDocument",
  26.  
  27. WIDTH = "width",
  28. HEIGHT = "height",
  29. PX = "px",
  30.  
  31. // HANDLE KEYS
  32. SHIM_DEFERRED = "shimdeferred",
  33. SHIM_RESIZE = "shimresize",
  34.  
  35. // Events
  36. VisibleChange = "visibleChange",
  37. WidthChange = "widthChange",
  38. HeightChange = "heightChange",
  39. ShimChange = "shimChange",
  40. ZIndexChange = "zIndexChange",
  41. ContentUpdate = "contentUpdate",
  42.  
  43. // CSS
  44. STACKED = "stacked";
  45.  
  46. /**
  47. * Widget extension, which can be used to add stackable (z-index) support to the
  48. * base Widget class along with a shimming solution, through the
  49. * <a href="Base.html#method_build">Base.build</a> method.
  50. *
  51. * @class WidgetStack
  52. * @param {Object} User configuration object
  53. */
  54. function Stack(config) {}
  55.  
  56. // Static Properties
  57. /**
  58. * Static property used to define the default attribute
  59. * configuration introduced by WidgetStack.
  60. *
  61. * @property ATTRS
  62. * @type Object
  63. * @static
  64. */
  65. Stack.ATTRS = {
  66. /**
  67. * @attribute shim
  68. * @type boolean
  69. * @default false, for all browsers other than IE6, for which a shim is enabled by default.
  70. *
  71. * @description Boolean flag to indicate whether or not a shim should be added to the Widgets
  72. * boundingBox, to protect it from select box bleedthrough.
  73. */
  74. shim: {
  75. value: (UA.ie == 6)
  76. },
  77.  
  78. /**
  79. * @attribute zIndex
  80. * @type number
  81. * @default 0
  82. * @description The z-index to apply to the Widgets boundingBox. Non-numerical values for
  83. * zIndex will be converted to 0
  84. */
  85. zIndex: {
  86. value : 0,
  87. setter: '_setZIndex'
  88. }
  89. };
  90.  
  91. /**
  92. * The HTML parsing rules for the WidgetStack class.
  93. *
  94. * @property HTML_PARSER
  95. * @static
  96. * @type Object
  97. */
  98. Stack.HTML_PARSER = {
  99. zIndex: function (srcNode) {
  100. return this._parseZIndex(srcNode);
  101. }
  102. };
  103.  
  104. /**
  105. * Default class used to mark the shim element
  106. *
  107. * @property SHIM_CLASS_NAME
  108. * @type String
  109. * @static
  110. * @default "yui3-widget-shim"
  111. */
  112. Stack.SHIM_CLASS_NAME = Widget.getClassName(SHIM);
  113.  
  114. /**
  115. * Default class used to mark the boundingBox of a stacked widget.
  116. *
  117. * @property STACKED_CLASS_NAME
  118. * @type String
  119. * @static
  120. * @default "yui3-widget-stacked"
  121. */
  122. Stack.STACKED_CLASS_NAME = Widget.getClassName(STACKED);
  123.  
  124. /**
  125. * Default markup template used to generate the shim element.
  126. *
  127. * @property SHIM_TEMPLATE
  128. * @type String
  129. * @static
  130. */
  131. Stack.SHIM_TEMPLATE = '<iframe class="' + Stack.SHIM_CLASS_NAME + '" frameborder="0" title="Widget Stacking Shim" src="javascript:false" tabindex="-1" role="presentation"></iframe>';
  132.  
  133. Stack.prototype = {
  134.  
  135. initializer : function() {
  136. this._stackNode = this.get(BOUNDING_BOX);
  137. this._stackHandles = {};
  138.  
  139. // WIDGET METHOD OVERLAP
  140. Y.after(this._renderUIStack, this, RENDER_UI);
  141. Y.after(this._syncUIStack, this, SYNC_UI);
  142. Y.after(this._bindUIStack, this, BIND_UI);
  143. },
  144.  
  145. /**
  146. * Synchronizes the UI to match the Widgets stack state. This method in
  147. * invoked after syncUI is invoked for the Widget class using YUI's aop infrastructure.
  148. *
  149. * @method _syncUIStack
  150. * @protected
  151. */
  152. _syncUIStack: function() {
  153. this._uiSetShim(this.get(SHIM));
  154. this._uiSetZIndex(this.get(ZINDEX));
  155. },
  156.  
  157. /**
  158. * Binds event listeners responsible for updating the UI state in response to
  159. * Widget stack related state changes.
  160. * <p>
  161. * This method is invoked after bindUI is invoked for the Widget class
  162. * using YUI's aop infrastructure.
  163. * </p>
  164. * @method _bindUIStack
  165. * @protected
  166. */
  167. _bindUIStack: function() {
  168. this.after(ShimChange, this._afterShimChange);
  169. this.after(ZIndexChange, this._afterZIndexChange);
  170. },
  171.  
  172. /**
  173. * Creates/Initializes the DOM to support stackability.
  174. * <p>
  175. * This method in invoked after renderUI is invoked for the Widget class
  176. * using YUI's aop infrastructure.
  177. * </p>
  178. * @method _renderUIStack
  179. * @protected
  180. */
  181. _renderUIStack: function() {
  182. this._stackNode.addClass(Stack.STACKED_CLASS_NAME);
  183. },
  184.  
  185. /**
  186. Parses a `zIndex` attribute value from this widget's `srcNode`.
  187.  
  188. @method _parseZIndex
  189. @param {Node} srcNode The node to parse a `zIndex` value from.
  190. @return {Mixed} The parsed `zIndex` value.
  191. @protected
  192. **/
  193. _parseZIndex: function (srcNode) {
  194. var zIndex;
  195.  
  196. // Prefers how WebKit handles `z-index` which better matches the
  197. // spec:
  198. //
  199. // * http://www.w3.org/TR/CSS2/visuren.html#z-index
  200. // * https://bugs.webkit.org/show_bug.cgi?id=15562
  201. //
  202. // When a node isn't rendered in the document, and/or when a
  203. // node is not positioned, then it doesn't have a context to derive
  204. // a valid `z-index` value from.
  205. if (!srcNode.inDoc() || srcNode.getStyle('position') === 'static') {
  206. zIndex = 'auto';
  207. } else {
  208. // Uses `getComputedStyle()` because it has greater accuracy in
  209. // more browsers than `getStyle()` does for `z-index`.
  210. zIndex = srcNode.getComputedStyle('zIndex');
  211. }
  212.  
  213. // This extension adds a stacking context to widgets, therefore a
  214. // `srcNode` witout a stacking context (i.e. "auto") will return
  215. // `null` from this DOM parser. This way the widget's default or
  216. // user provided value for `zIndex` will be used.
  217. return zIndex === 'auto' ? null : zIndex;
  218. },
  219.  
  220. /**
  221. * Default setter for zIndex attribute changes. Normalizes zIndex values to
  222. * numbers, converting non-numerical values to 0.
  223. *
  224. * @method _setZIndex
  225. * @protected
  226. * @param {String | Number} zIndex
  227. * @return {Number} Normalized zIndex
  228. */
  229. _setZIndex: function(zIndex) {
  230. if (L.isString(zIndex)) {
  231. zIndex = parseInt(zIndex, 10);
  232. }
  233. if (!L.isNumber(zIndex)) {
  234. zIndex = 0;
  235. }
  236. return zIndex;
  237. },
  238.  
  239. /**
  240. * Default attribute change listener for the shim attribute, responsible
  241. * for updating the UI, in response to attribute changes.
  242. *
  243. * @method _afterShimChange
  244. * @protected
  245. * @param {EventFacade} e The event facade for the attribute change
  246. */
  247. _afterShimChange : function(e) {
  248. this._uiSetShim(e.newVal);
  249. },
  250.  
  251. /**
  252. * Default attribute change listener for the zIndex attribute, responsible
  253. * for updating the UI, in response to attribute changes.
  254. *
  255. * @method _afterZIndexChange
  256. * @protected
  257. * @param {EventFacade} e The event facade for the attribute change
  258. */
  259. _afterZIndexChange : function(e) {
  260. this._uiSetZIndex(e.newVal);
  261. },
  262.  
  263. /**
  264. * Updates the UI to reflect the zIndex value passed in.
  265. *
  266. * @method _uiSetZIndex
  267. * @protected
  268. * @param {number} zIndex The zindex to be reflected in the UI
  269. */
  270. _uiSetZIndex: function (zIndex) {
  271. this._stackNode.setStyle(ZINDEX, zIndex);
  272. },
  273.  
  274. /**
  275. * Updates the UI to enable/disable the shim. If the widget is not currently visible,
  276. * creation of the shim is deferred until it is made visible, for performance reasons.
  277. *
  278. * @method _uiSetShim
  279. * @protected
  280. * @param {boolean} enable If true, creates/renders the shim, if false, removes it.
  281. */
  282. _uiSetShim: function (enable) {
  283. if (enable) {
  284. // Lazy creation
  285. if (this.get(VISIBLE)) {
  286. this._renderShim();
  287. } else {
  288. this._renderShimDeferred();
  289. }
  290.  
  291. // Eagerly attach resize handlers
  292. //
  293. // Required because of Event stack behavior, commit ref: cd8dddc
  294. // Should be revisted after Ticket #2531067 is resolved.
  295. if (UA.ie == 6) {
  296. this._addShimResizeHandlers();
  297. }
  298. } else {
  299. this._destroyShim();
  300. }
  301. },
  302.  
  303. /**
  304. * Sets up change handlers for the visible attribute, to defer shim creation/rendering
  305. * until the Widget is made visible.
  306. *
  307. * @method _renderShimDeferred
  308. * @private
  309. */
  310. _renderShimDeferred : function() {
  311.  
  312. this._stackHandles[SHIM_DEFERRED] = this._stackHandles[SHIM_DEFERRED] || [];
  313.  
  314. var handles = this._stackHandles[SHIM_DEFERRED],
  315. createBeforeVisible = function(e) {
  316. if (e.newVal) {
  317. this._renderShim();
  318. }
  319. };
  320.  
  321. handles.push(this.on(VisibleChange, createBeforeVisible));
  322. // Depending how how Ticket #2531067 is resolved, a reversal of
  323. // commit ref: cd8dddc could lead to a more elagent solution, with
  324. // the addition of this line here:
  325. //
  326. // handles.push(this.after(VisibleChange, this.sizeShim));
  327. },
  328.  
  329. /**
  330. * Sets up event listeners to resize the shim when the size of the Widget changes.
  331. * <p>
  332. * NOTE: This method is only used for IE6 currently, since IE6 doesn't support a way to
  333. * resize the shim purely through CSS, when the Widget does not have an explicit width/height
  334. * set.
  335. * </p>
  336. * @method _addShimResizeHandlers
  337. * @private
  338. */
  339. _addShimResizeHandlers : function() {
  340.  
  341. this._stackHandles[SHIM_RESIZE] = this._stackHandles[SHIM_RESIZE] || [];
  342.  
  343. var sizeShim = this.sizeShim,
  344. handles = this._stackHandles[SHIM_RESIZE];
  345.  
  346. handles.push(this.after(VisibleChange, sizeShim));
  347. handles.push(this.after(WidthChange, sizeShim));
  348. handles.push(this.after(HeightChange, sizeShim));
  349. handles.push(this.after(ContentUpdate, sizeShim));
  350. },
  351.  
  352. /**
  353. * Detaches any handles stored for the provided key
  354. *
  355. * @method _detachStackHandles
  356. * @param String handleKey The key defining the group of handles which should be detached
  357. * @private
  358. */
  359. _detachStackHandles : function(handleKey) {
  360. var handles = this._stackHandles[handleKey],
  361. handle;
  362.  
  363. if (handles && handles.length > 0) {
  364. while((handle = handles.pop())) {
  365. handle.detach();
  366. }
  367. }
  368. },
  369.  
  370. /**
  371. * Creates the shim element and adds it to the DOM
  372. *
  373. * @method _renderShim
  374. * @private
  375. */
  376. _renderShim : function() {
  377. var shimEl = this._shimNode,
  378. stackEl = this._stackNode;
  379.  
  380. if (!shimEl) {
  381. shimEl = this._shimNode = this._getShimTemplate();
  382. stackEl.insertBefore(shimEl, stackEl.get(FIRST_CHILD));
  383.  
  384. this._detachStackHandles(SHIM_DEFERRED);
  385. this.sizeShim();
  386. }
  387. },
  388.  
  389. /**
  390. * Removes the shim from the DOM, and detaches any related event
  391. * listeners.
  392. *
  393. * @method _destroyShim
  394. * @private
  395. */
  396. _destroyShim : function() {
  397. if (this._shimNode) {
  398. this._shimNode.get(PARENT_NODE).removeChild(this._shimNode);
  399. this._shimNode = null;
  400.  
  401. this._detachStackHandles(SHIM_DEFERRED);
  402. this._detachStackHandles(SHIM_RESIZE);
  403. }
  404. },
  405.  
  406. /**
  407. * For IE6, synchronizes the size and position of iframe shim to that of
  408. * Widget bounding box which it is protecting. For all other browsers,
  409. * this method does not do anything.
  410. *
  411. * @method sizeShim
  412. */
  413. sizeShim: function () {
  414. var shim = this._shimNode,
  415. node = this._stackNode;
  416.  
  417. if (shim && UA.ie === 6 && this.get(VISIBLE)) {
  418. shim.setStyle(WIDTH, node.get(OFFSET_WIDTH) + PX);
  419. shim.setStyle(HEIGHT, node.get(OFFSET_HEIGHT) + PX);
  420. }
  421. },
  422.  
  423. /**
  424. * Creates a cloned shim node, using the SHIM_TEMPLATE html template, for use on a new instance.
  425. *
  426. * @method _getShimTemplate
  427. * @private
  428. * @return {Node} node A new shim Node instance.
  429. */
  430. _getShimTemplate : function() {
  431. return Node.create(Stack.SHIM_TEMPLATE, this._stackNode.get(OWNER_DOCUMENT));
  432. }
  433. };
  434.  
  435. Y.WidgetStack = Stack;
  436.