API Docs for: 3.17.2
Show:

File: widget-position-align/js/Widget-PositionAlign.js

  1. /**
  2. Provides extended/advanced XY positioning support for Widgets, through an
  3. extension.
  4.  
  5. It builds on top of the `widget-position` module, to provide alignment and
  6. centering support. Future releases aim to add constrained and fixed positioning
  7. support.
  8.  
  9. @module widget-position-align
  10. **/
  11. var Lang = Y.Lang,
  12.  
  13. ALIGN = 'align',
  14. ALIGN_ON = 'alignOn',
  15.  
  16. VISIBLE = 'visible',
  17. BOUNDING_BOX = 'boundingBox',
  18.  
  19. OFFSET_WIDTH = 'offsetWidth',
  20. OFFSET_HEIGHT = 'offsetHeight',
  21. REGION = 'region',
  22. VIEWPORT_REGION = 'viewportRegion';
  23.  
  24. /**
  25. Widget extension, which can be used to add extended XY positioning support to
  26. the base Widget class, through the `Base.create` method.
  27.  
  28. **Note:** This extension requires that the `WidgetPosition` extension be added
  29. to the Widget (before `WidgetPositionAlign`, if part of the same extension list
  30. passed to `Base.build`).
  31.  
  32. @class WidgetPositionAlign
  33. @param {Object} config User configuration object.
  34. @constructor
  35. **/
  36. function PositionAlign (config) {}
  37.  
  38. PositionAlign.ATTRS = {
  39.  
  40. /**
  41. The alignment configuration for this widget.
  42.  
  43. The `align` attribute is used to align a reference point on the widget, with
  44. the reference point on another `Node`, or the viewport. The object which
  45. `align` expects has the following properties:
  46.  
  47. * __`node`__: The `Node` to which the widget is to be aligned. If set to
  48. `null`, or not provided, the widget is aligned to the viewport.
  49.  
  50. * __`points`__: A two element Array, defining the two points on the widget
  51. and `Node`/viewport which are to be aligned. The first element is the
  52. point on the widget, and the second element is the point on the
  53. `Node`/viewport. Supported alignment points are defined as static
  54. properties on `WidgetPositionAlign`.
  55.  
  56. @example Aligns the top-right corner of the widget with the top-left corner
  57. of the viewport:
  58.  
  59. myWidget.set('align', {
  60. points: [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.TL]
  61. });
  62.  
  63. @attribute align
  64. @type Object
  65. @default null
  66. **/
  67. align: {
  68. value: null
  69. },
  70.  
  71. /**
  72. A convenience Attribute, which can be used as a shortcut for the `align`
  73. Attribute.
  74.  
  75. If set to `true`, the widget is centered in the viewport. If set to a `Node`
  76. reference or valid selector String, the widget will be centered within the
  77. `Node`. If set to `false`, no center positioning is applied.
  78.  
  79. @attribute centered
  80. @type Boolean|Node
  81. @default false
  82. **/
  83. centered: {
  84. setter : '_setAlignCenter',
  85. lazyAdd:false,
  86. value :false
  87. },
  88.  
  89. /**
  90. An Array of Objects corresponding to the `Node`s and events that will cause
  91. the alignment of this widget to be synced to the DOM.
  92.  
  93. The `alignOn` Attribute is expected to be an Array of Objects with the
  94. following properties:
  95.  
  96. * __`eventName`__: The String event name to listen for.
  97.  
  98. * __`node`__: The optional `Node` that will fire the event, it can be a
  99. `Node` reference or a selector String. This will default to the widget's
  100. `boundingBox`.
  101.  
  102. @example Sync this widget's alignment on window resize:
  103.  
  104. myWidget.set('alignOn', [
  105. {
  106. node : Y.one('win'),
  107. eventName: 'resize'
  108. }
  109. ]);
  110.  
  111. @attribute alignOn
  112. @type Array
  113. @default []
  114. **/
  115. alignOn: {
  116. value : [],
  117. validator: Y.Lang.isArray
  118. }
  119. };
  120.  
  121. /**
  122. Constant used to specify the top-left corner for alignment
  123.  
  124. @property TL
  125. @type String
  126. @value 'tl'
  127. @static
  128. **/
  129. PositionAlign.TL = 'tl';
  130.  
  131. /**
  132. Constant used to specify the top-right corner for alignment
  133.  
  134. @property TR
  135. @type String
  136. @value 'tr'
  137. @static
  138. **/
  139. PositionAlign.TR = 'tr';
  140.  
  141. /**
  142. Constant used to specify the bottom-left corner for alignment
  143.  
  144. @property BL
  145. @type String
  146. @value 'bl'
  147. @static
  148. **/
  149. PositionAlign.BL = 'bl';
  150.  
  151. /**
  152. Constant used to specify the bottom-right corner for alignment
  153.  
  154. @property BR
  155. @type String
  156. @value 'br'
  157. @static
  158. **/
  159. PositionAlign.BR = 'br';
  160.  
  161. /**
  162. Constant used to specify the top edge-center point for alignment
  163.  
  164. @property TC
  165. @type String
  166. @value 'tc'
  167. @static
  168. **/
  169. PositionAlign.TC = 'tc';
  170.  
  171. /**
  172. Constant used to specify the right edge, center point for alignment
  173.  
  174. @property RC
  175. @type String
  176. @value 'rc'
  177. @static
  178. **/
  179. PositionAlign.RC = 'rc';
  180.  
  181. /**
  182. Constant used to specify the bottom edge, center point for alignment
  183.  
  184. @property BC
  185. @type String
  186. @value 'bc'
  187. @static
  188. **/
  189. PositionAlign.BC = 'bc';
  190.  
  191. /**
  192. Constant used to specify the left edge, center point for alignment
  193.  
  194. @property LC
  195. @type String
  196. @value 'lc'
  197. @static
  198. **/
  199. PositionAlign.LC = 'lc';
  200.  
  201. /**
  202. Constant used to specify the center of widget/node/viewport for alignment
  203.  
  204. @property CC
  205. @type String
  206. @value 'cc'
  207. @static
  208. */
  209. PositionAlign.CC = 'cc';
  210.  
  211. PositionAlign.prototype = {
  212. // -- Protected Properties -------------------------------------------------
  213.  
  214.  
  215. initializer : function() {
  216. if (!this._posNode) {
  217. Y.error('WidgetPosition needs to be added to the Widget, ' +
  218. 'before WidgetPositionAlign is added');
  219. }
  220.  
  221. Y.after(this._bindUIPosAlign, this, 'bindUI');
  222. Y.after(this._syncUIPosAlign, this, 'syncUI');
  223. },
  224.  
  225. /**
  226. Holds the alignment-syncing event handles.
  227.  
  228. @property _posAlignUIHandles
  229. @type Array
  230. @default null
  231. @protected
  232. **/
  233. _posAlignUIHandles: null,
  234.  
  235. // -- Lifecycle Methods ----------------------------------------------------
  236.  
  237. destructor: function () {
  238. this._detachPosAlignUIHandles();
  239. },
  240.  
  241. /**
  242. Bind event listeners responsible for updating the UI state in response to
  243. the widget's position-align related state changes.
  244.  
  245. This method is invoked after `bindUI` has been invoked for the `Widget`
  246. class using the AOP infrastructure.
  247.  
  248. @method _bindUIPosAlign
  249. @protected
  250. **/
  251. _bindUIPosAlign: function () {
  252. this.after('alignChange', this._afterAlignChange);
  253. this.after('alignOnChange', this._afterAlignOnChange);
  254. this.after('visibleChange', this._syncUIPosAlign);
  255. },
  256.  
  257. /**
  258. Synchronizes the current `align` Attribute value to the DOM.
  259.  
  260. This method is invoked after `syncUI` has been invoked for the `Widget`
  261. class using the AOP infrastructure.
  262.  
  263. @method _syncUIPosAlign
  264. @protected
  265. **/
  266. _syncUIPosAlign: function () {
  267. var align = this.get(ALIGN);
  268.  
  269. this._uiSetVisiblePosAlign(this.get(VISIBLE));
  270.  
  271. if (align) {
  272. this._uiSetAlign(align.node, align.points);
  273. }
  274. },
  275.  
  276. // -- Public Methods -------------------------------------------------------
  277.  
  278. /**
  279. Aligns this widget to the provided `Node` (or viewport) using the provided
  280. points. This method can be invoked with no arguments which will cause the
  281. widget's current `align` Attribute value to be synced to the DOM.
  282.  
  283. @example Aligning to the top-left corner of the `<body>`:
  284.  
  285. myWidget.align('body',
  286. [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.TR]);
  287.  
  288. @method align
  289. @param {Node|String|null} [node] A reference (or selector String) for the
  290. `Node` which with the widget is to be aligned. If null is passed in, the
  291. widget will be aligned with the viewport.
  292. @param {Array[2]} [points] A two item array specifying the points on the
  293. widget and `Node`/viewport which will to be aligned. The first entry is
  294. the point on the widget, and the second entry is the point on the
  295. `Node`/viewport. Valid point references are defined as static constants on
  296. the `WidgetPositionAlign` extension.
  297. @chainable
  298. **/
  299. align: function (node, points) {
  300. if (arguments.length) {
  301. // Set the `align` Attribute.
  302. this.set(ALIGN, {
  303. node : node,
  304. points: points
  305. });
  306. } else {
  307. // Sync the current `align` Attribute value to the DOM.
  308. this._syncUIPosAlign();
  309. }
  310.  
  311. return this;
  312. },
  313.  
  314. /**
  315. Centers the widget in the viewport, or if a `Node` is passed in, it will
  316. be centered to that `Node`.
  317.  
  318. @method centered
  319. @param {Node|String} [node] A `Node` reference or selector String defining
  320. the `Node` which the widget should be centered. If a `Node` is not passed
  321. in, then the widget will be centered to the viewport.
  322. @chainable
  323. **/
  324. centered: function (node) {
  325. return this.align(node, [PositionAlign.CC, PositionAlign.CC]);
  326. },
  327.  
  328. // -- Protected Methods ----------------------------------------------------
  329.  
  330. /**
  331. Default setter for `center` Attribute changes. Sets up the appropriate
  332. value, and passes it through the to the align attribute.
  333.  
  334. @method _setAlignCenter
  335. @param {Boolean|Node} val The Attribute value being set.
  336. @return {Boolean|Node} the value passed in.
  337. @protected
  338. **/
  339. _setAlignCenter: function (val) {
  340. if (val) {
  341. this.set(ALIGN, {
  342. node : val === true ? null : val,
  343. points: [PositionAlign.CC, PositionAlign.CC]
  344. });
  345. }
  346.  
  347. return val;
  348. },
  349.  
  350. /**
  351. Updates the UI to reflect the `align` value passed in.
  352.  
  353. **Note:** See the `align` Attribute documentation, for the Object structure
  354. expected.
  355.  
  356. @method _uiSetAlign
  357. @param {Node|String|null} [node] The node to align to, or null to indicate
  358. the viewport.
  359. @param {Array} points The alignment points.
  360. @protected
  361. **/
  362. _uiSetAlign: function (node, points) {
  363. if ( ! Lang.isArray(points) || points.length !== 2) {
  364. Y.error('align: Invalid Points Arguments');
  365. return;
  366. }
  367.  
  368. var nodeRegion = this._getRegion(node),
  369. widgetPoint, nodePoint, xy;
  370.  
  371. if ( ! nodeRegion) {
  372. // No-op, nothing to align to.
  373. return;
  374. }
  375.  
  376. widgetPoint = points[0];
  377. nodePoint = points[1];
  378.  
  379. // TODO: Optimize KWeight - Would lookup table help?
  380. switch (nodePoint) {
  381. case PositionAlign.TL:
  382. xy = [nodeRegion.left, nodeRegion.top];
  383. break;
  384.  
  385. case PositionAlign.TR:
  386. xy = [nodeRegion.right, nodeRegion.top];
  387. break;
  388.  
  389. case PositionAlign.BL:
  390. xy = [nodeRegion.left, nodeRegion.bottom];
  391. break;
  392.  
  393. case PositionAlign.BR:
  394. xy = [nodeRegion.right, nodeRegion.bottom];
  395. break;
  396.  
  397. case PositionAlign.TC:
  398. xy = [
  399. nodeRegion.left + Math.floor(nodeRegion.width / 2),
  400. nodeRegion.top
  401. ];
  402. break;
  403.  
  404. case PositionAlign.BC:
  405. xy = [
  406. nodeRegion.left + Math.floor(nodeRegion.width / 2),
  407. nodeRegion.bottom
  408. ];
  409. break;
  410.  
  411. case PositionAlign.LC:
  412. xy = [
  413. nodeRegion.left,
  414. nodeRegion.top + Math.floor(nodeRegion.height / 2)
  415. ];
  416. break;
  417.  
  418. case PositionAlign.RC:
  419. xy = [
  420. nodeRegion.right,
  421. nodeRegion.top + Math.floor(nodeRegion.height / 2)
  422. ];
  423. break;
  424.  
  425. case PositionAlign.CC:
  426. xy = [
  427. nodeRegion.left + Math.floor(nodeRegion.width / 2),
  428. nodeRegion.top + Math.floor(nodeRegion.height / 2)
  429. ];
  430. break;
  431.  
  432. default:
  433. Y.log('align: Invalid Points Arguments', 'info',
  434. 'widget-position-align');
  435. break;
  436.  
  437. }
  438.  
  439. if (xy) {
  440. this._doAlign(widgetPoint, xy[0], xy[1]);
  441. }
  442. },
  443.  
  444. /**
  445. Attaches or detaches alignment-syncing event handlers based on the widget's
  446. `visible` Attribute state.
  447.  
  448. @method _uiSetVisiblePosAlign
  449. @param {Boolean} visible The current value of the widget's `visible`
  450. Attribute.
  451. @protected
  452. **/
  453. _uiSetVisiblePosAlign: function (visible) {
  454. if (visible) {
  455. this._attachPosAlignUIHandles();
  456. } else {
  457. this._detachPosAlignUIHandles();
  458. }
  459. },
  460.  
  461. /**
  462. Attaches the alignment-syncing event handlers.
  463.  
  464. @method _attachPosAlignUIHandles
  465. @protected
  466. **/
  467. _attachPosAlignUIHandles: function () {
  468. if (this._posAlignUIHandles) {
  469. // No-op if we have already setup the event handlers.
  470. return;
  471. }
  472.  
  473. var bb = this.get(BOUNDING_BOX),
  474. syncAlign = Y.bind(this._syncUIPosAlign, this),
  475. handles = [];
  476.  
  477. Y.Array.each(this.get(ALIGN_ON), function (o) {
  478. var event = o.eventName,
  479. node = Y.one(o.node) || bb;
  480.  
  481. if (event) {
  482. handles.push(node.on(event, syncAlign));
  483. }
  484. });
  485.  
  486. this._posAlignUIHandles = handles;
  487. },
  488.  
  489. /**
  490. Detaches the alignment-syncing event handlers.
  491.  
  492. @method _detachPosAlignUIHandles
  493. @protected
  494. **/
  495. _detachPosAlignUIHandles: function () {
  496. var handles = this._posAlignUIHandles;
  497. if (handles) {
  498. new Y.EventHandle(handles).detach();
  499. this._posAlignUIHandles = null;
  500. }
  501. },
  502.  
  503. // -- Private Methods ------------------------------------------------------
  504.  
  505. /**
  506. Helper method, used to align the given point on the widget, with the XY page
  507. coordinates provided.
  508.  
  509. @method _doAlign
  510. @param {String} widgetPoint Supported point constant
  511. (e.g. WidgetPositionAlign.TL)
  512. @param {Number} x X page coordinate to align to.
  513. @param {Number} y Y page coordinate to align to.
  514. @private
  515. **/
  516. _doAlign: function (widgetPoint, x, y) {
  517. var widgetNode = this._posNode,
  518. xy;
  519.  
  520. switch (widgetPoint) {
  521. case PositionAlign.TL:
  522. xy = [x, y];
  523. break;
  524.  
  525. case PositionAlign.TR:
  526. xy = [
  527. x - widgetNode.get(OFFSET_WIDTH),
  528. y
  529. ];
  530. break;
  531.  
  532. case PositionAlign.BL:
  533. xy = [
  534. x,
  535. y - widgetNode.get(OFFSET_HEIGHT)
  536. ];
  537. break;
  538.  
  539. case PositionAlign.BR:
  540. xy = [
  541. x - widgetNode.get(OFFSET_WIDTH),
  542. y - widgetNode.get(OFFSET_HEIGHT)
  543. ];
  544. break;
  545.  
  546. case PositionAlign.TC:
  547. xy = [
  548. x - (widgetNode.get(OFFSET_WIDTH) / 2),
  549. y
  550. ];
  551. break;
  552.  
  553. case PositionAlign.BC:
  554. xy = [
  555. x - (widgetNode.get(OFFSET_WIDTH) / 2),
  556. y - widgetNode.get(OFFSET_HEIGHT)
  557. ];
  558. break;
  559.  
  560. case PositionAlign.LC:
  561. xy = [
  562. x,
  563. y - (widgetNode.get(OFFSET_HEIGHT) / 2)
  564. ];
  565. break;
  566.  
  567. case PositionAlign.RC:
  568. xy = [
  569. x - widgetNode.get(OFFSET_WIDTH),
  570. y - (widgetNode.get(OFFSET_HEIGHT) / 2)
  571. ];
  572. break;
  573.  
  574. case PositionAlign.CC:
  575. xy = [
  576. x - (widgetNode.get(OFFSET_WIDTH) / 2),
  577. y - (widgetNode.get(OFFSET_HEIGHT) / 2)
  578. ];
  579. break;
  580.  
  581. default:
  582. Y.log('align: Invalid Points Argument', 'info',
  583. 'widget-position-align');
  584. break;
  585.  
  586. }
  587.  
  588. if (xy) {
  589. this.move(xy);
  590. }
  591. },
  592.  
  593. /**
  594. Returns the region of the passed-in `Node`, or the viewport region if
  595. calling with passing in a `Node`.
  596.  
  597. @method _getRegion
  598. @param {Node} [node] The node to get the region of.
  599. @return {Object} The node's region.
  600. @private
  601. **/
  602. _getRegion: function (node) {
  603. var nodeRegion;
  604.  
  605. if ( ! node) {
  606. nodeRegion = this._posNode.get(VIEWPORT_REGION);
  607. } else {
  608. node = Y.Node.one(node);
  609. if (node) {
  610. nodeRegion = node.get(REGION);
  611. }
  612. }
  613.  
  614. return nodeRegion;
  615. },
  616.  
  617. // -- Protected Event Handlers ---------------------------------------------
  618.  
  619. /**
  620. Handles `alignChange` events by updating the UI in response to `align`
  621. Attribute changes.
  622.  
  623. @method _afterAlignChange
  624. @param {EventFacade} e
  625. @protected
  626. **/
  627. _afterAlignChange: function (e) {
  628. var align = e.newVal;
  629. if (align) {
  630. this._uiSetAlign(align.node, align.points);
  631. }
  632. },
  633.  
  634. /**
  635. Handles `alignOnChange` events by updating the alignment-syncing event
  636. handlers.
  637.  
  638. @method _afterAlignOnChange
  639. @param {EventFacade} e
  640. @protected
  641. **/
  642. _afterAlignOnChange: function(e) {
  643. this._detachPosAlignUIHandles();
  644.  
  645. if (this.get(VISIBLE)) {
  646. this._attachPosAlignUIHandles();
  647. }
  648. }
  649. };
  650.  
  651. Y.WidgetPositionAlign = PositionAlign;
  652.