PageRenderTime 26ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/widget-stack/js/Widget-Stack.js

https://bitbucket.org/Quartermain/yui3
JavaScript | 433 lines | 164 code | 48 blank | 221 comment | 22 complexity | e989e9e8711ab9761e7ca4966e384727 MD5 | raw file
  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;