API Docs for: 3.8.0
Show:

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

  1. /**
  2. Provides extended/advanced XY positioning support for Widgets, through an
  3. extension.

  4. It builds on top of the `widget-position` module, to provide alignment and
  5. centering support. Future releases aim to add constrained and fixed positioning
  6. support.

  7. @module widget-position-align
  8. **/
  9. var Lang = Y.Lang,

  10.     ALIGN        = 'align',
  11.     ALIGN_ON     = 'alignOn',

  12.     VISIBLE      = 'visible',
  13.     BOUNDING_BOX = 'boundingBox',

  14.     OFFSET_WIDTH    = 'offsetWidth',
  15.     OFFSET_HEIGHT   = 'offsetHeight',
  16.     REGION          = 'region',
  17.     VIEWPORT_REGION = 'viewportRegion';

  18. /**
  19. Widget extension, which can be used to add extended XY positioning support to
  20. the base Widget class, through the `Base.create` method.

  21. **Note:** This extension requires that the `WidgetPosition` extension be added
  22. to the Widget (before `WidgetPositionAlign`, if part of the same extension list
  23. passed to `Base.build`).

  24. @class WidgetPositionAlign
  25. @param {Object} config User configuration object.
  26. @constructor
  27. **/
  28. function PositionAlign (config) {
  29.     if ( ! this._posNode) {
  30.         Y.error('WidgetPosition needs to be added to the Widget, ' +
  31.             'before WidgetPositionAlign is added');
  32.     }

  33.     Y.after(this._bindUIPosAlign, this, 'bindUI');
  34.     Y.after(this._syncUIPosAlign, this, 'syncUI');
  35. }

  36. PositionAlign.ATTRS = {

  37.     /**
  38.     The alignment configuration for this widget.

  39.     The `align` attribute is used to align a reference point on the widget, with
  40.     the reference point on another `Node`, or the viewport. The object which
  41.     `align` expects has the following properties:

  42.       * __`node`__: The `Node` to which the widget is to be aligned. If set to
  43.         `null`, or not provided, the widget is aligned to the viewport.
  44.    
  45.       * __`points`__: A two element Array, defining the two points on the widget
  46.         and `Node`/viewport which are to be aligned. The first element is the
  47.         point on the widget, and the second element is the point on the
  48.         `Node`/viewport. Supported alignment points are defined as static
  49.         properties on `WidgetPositionAlign`.
  50.    
  51.     @example Aligns the top-right corner of the widget with the top-left corner
  52.     of the viewport:

  53.         myWidget.set('align', {
  54.             points: [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.TL]
  55.         });
  56.    
  57.     @attribute align
  58.     @type Object
  59.     @default null
  60.     **/
  61.     align: {
  62.         value: null
  63.     },

  64.     /**
  65.     A convenience Attribute, which can be used as a shortcut for the `align`
  66.     Attribute.
  67.    
  68.     If set to `true`, the widget is centered in the viewport. If set to a `Node`
  69.     reference or valid selector String, the widget will be centered within the
  70.     `Node`. If set to `false`, no center positioning is applied.

  71.     @attribute centered
  72.     @type Boolean|Node
  73.     @default false
  74.     **/
  75.     centered: {
  76.         setter : '_setAlignCenter',
  77.         lazyAdd:false,
  78.         value  :false
  79.     },

  80.     /**
  81.     An Array of Objects corresponding to the `Node`s and events that will cause
  82.     the alignment of this widget to be synced to the DOM.

  83.     The `alignOn` Attribute is expected to be an Array of Objects with the
  84.     following properties:

  85.       * __`eventName`__: The String event name to listen for.

  86.       * __`node`__: The optional `Node` that will fire the event, it can be a
  87.         `Node` reference or a selector String. This will default to the widget's
  88.         `boundingBox`.

  89.     @example Sync this widget's alignment on window resize:

  90.         myWidget.set('alignOn', [
  91.             {
  92.                 node     : Y.one('win'),
  93.                 eventName: 'resize'
  94.             }
  95.         ]);

  96.     @attribute alignOn
  97.     @type Array
  98.     @default []
  99.     **/
  100.     alignOn: {
  101.         value    : [],
  102.         validator: Y.Lang.isArray
  103.     }
  104. };

  105. /**
  106. Constant used to specify the top-left corner for alignment

  107. @property TL
  108. @type String
  109. @value 'tl'
  110. @static
  111. **/
  112. PositionAlign.TL = 'tl';

  113. /**
  114. Constant used to specify the top-right corner for alignment
  115.  
  116. @property TR
  117. @type String
  118. @value 'tr'
  119. @static
  120. **/
  121. PositionAlign.TR = 'tr';

  122. /**
  123. Constant used to specify the bottom-left corner for alignment
  124.  
  125. @property BL
  126. @type String
  127. @value 'bl'
  128. @static
  129. **/
  130. PositionAlign.BL = 'bl';

  131. /**
  132. Constant used to specify the bottom-right corner for alignment

  133. @property BR
  134. @type String
  135. @value 'br'
  136. @static
  137. **/
  138. PositionAlign.BR = 'br';

  139. /**
  140. Constant used to specify the top edge-center point for alignment

  141. @property TC
  142. @type String
  143. @value 'tc'
  144. @static
  145. **/
  146. PositionAlign.TC = 'tc';

  147. /**
  148. Constant used to specify the right edge, center point for alignment
  149.  
  150. @property RC
  151. @type String
  152. @value 'rc'
  153. @static
  154. **/
  155. PositionAlign.RC = 'rc';

  156. /**
  157. Constant used to specify the bottom edge, center point for alignment
  158.  
  159. @property BC
  160. @type String
  161. @value 'bc'
  162. @static
  163. **/
  164. PositionAlign.BC = 'bc';

  165. /**
  166. Constant used to specify the left edge, center point for alignment
  167.  
  168. @property LC
  169. @type String
  170. @value 'lc'
  171. @static
  172. **/
  173. PositionAlign.LC = 'lc';

  174. /**
  175. Constant used to specify the center of widget/node/viewport for alignment

  176. @property CC
  177. @type String
  178. @value 'cc'
  179. @static
  180. */
  181. PositionAlign.CC = 'cc';

  182. PositionAlign.prototype = {
  183.     // -- Protected Properties -------------------------------------------------

  184.     /**
  185.     Holds the alignment-syncing event handles.

  186.     @property _posAlignUIHandles
  187.     @type Array
  188.     @default null
  189.     @protected
  190.     **/
  191.     _posAlignUIHandles: null,

  192.     // -- Lifecycle Methods ----------------------------------------------------

  193.     destructor: function () {
  194.         this._detachPosAlignUIHandles();
  195.     },

  196.     /**
  197.     Bind event listeners responsible for updating the UI state in response to
  198.     the widget's position-align related state changes.

  199.     This method is invoked after `bindUI` has been invoked for the `Widget`
  200.     class using the AOP infrastructure.

  201.     @method _bindUIPosAlign
  202.     @protected
  203.     **/
  204.     _bindUIPosAlign: function () {
  205.         this.after('alignChange', this._afterAlignChange);
  206.         this.after('alignOnChange', this._afterAlignOnChange);
  207.         this.after('visibleChange', this._syncUIPosAlign);
  208.     },

  209.     /**
  210.     Synchronizes the current `align` Attribute value to the DOM.

  211.     This method is invoked after `syncUI` has been invoked for the `Widget`
  212.     class using the AOP infrastructure.

  213.     @method _syncUIPosAlign
  214.     @protected
  215.     **/
  216.     _syncUIPosAlign: function () {
  217.         var align = this.get(ALIGN);

  218.         this._uiSetVisiblePosAlign(this.get(VISIBLE));

  219.         if (align) {
  220.             this._uiSetAlign(align.node, align.points);
  221.         }
  222.     },

  223.     // -- Public Methods -------------------------------------------------------

  224.     /**
  225.     Aligns this widget to the provided `Node` (or viewport) using the provided
  226.     points. This method can be invoked with no arguments which will cause the
  227.     widget's current `align` Attribute value to be synced to the DOM.

  228.     @example Aligning to the top-left corner of the `<body>`:

  229.         myWidget.align('body',
  230.             [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.TR]);

  231.     @method align
  232.     @param {Node|String|null} [node] A reference (or selector String) for the
  233.       `Node` which with the widget is to be aligned. If null is passed in, the
  234.       widget will be aligned with the viewport.
  235.     @param {Array[2]} [points] A two item array specifying the points on the
  236.       widget and `Node`/viewport which will to be aligned. The first entry is
  237.       the point on the widget, and the second entry is the point on the
  238.       `Node`/viewport. Valid point references are defined as static constants on
  239.       the `WidgetPositionAlign` extension.
  240.     @chainable
  241.     **/
  242.     align: function (node, points) {
  243.         if (arguments.length) {
  244.             // Set the `align` Attribute.
  245.             this.set(ALIGN, {
  246.                 node  : node,
  247.                 points: points
  248.             });
  249.         } else {
  250.             // Sync the current `align` Attribute value to the DOM.
  251.             this._syncUIPosAlign();
  252.         }

  253.         return this;
  254.     },

  255.     /**
  256.     Centers the widget in the viewport, or if a `Node` is passed in, it will
  257.     be centered to that `Node`.

  258.     @method centered
  259.     @param {Node|String} [node] A `Node` reference or selector String defining
  260.       the `Node` which the widget should be centered. If a `Node` is not  passed
  261.       in, then the widget will be centered to the viewport.
  262.     @chainable
  263.     **/
  264.     centered: function (node) {
  265.         return this.align(node, [PositionAlign.CC, PositionAlign.CC]);
  266.     },

  267.     // -- Protected Methods ----------------------------------------------------

  268.     /**
  269.     Default setter for `center` Attribute changes. Sets up the appropriate
  270.     value, and passes it through the to the align attribute.

  271.     @method _setAlignCenter
  272.     @param {Boolean|Node} val The Attribute value being set.
  273.     @return {Boolean|Node} the value passed in.
  274.     @protected
  275.     **/
  276.     _setAlignCenter: function (val) {
  277.         if (val) {
  278.             this.set(ALIGN, {
  279.                 node  : val === true ? null : val,
  280.                 points: [PositionAlign.CC, PositionAlign.CC]
  281.             });
  282.         }

  283.         return val;
  284.     },

  285.     /**
  286.     Updates the UI to reflect the `align` value passed in.

  287.     **Note:** See the `align` Attribute documentation, for the Object structure
  288.     expected.

  289.     @method _uiSetAlign
  290.     @param {Node|String|null} [node] The node to align to, or null to indicate
  291.       the viewport.
  292.     @param {Array} points The alignment points.
  293.     @protected
  294.     **/
  295.     _uiSetAlign: function (node, points) {
  296.         if ( ! Lang.isArray(points) || points.length !== 2) {
  297.             Y.error('align: Invalid Points Arguments');
  298.             return;
  299.         }

  300.         var nodeRegion = this._getRegion(node),
  301.             widgetPoint, nodePoint, xy;

  302.         if ( ! nodeRegion) {
  303.             // No-op, nothing to align to.
  304.             return;
  305.         }

  306.         widgetPoint = points[0];
  307.         nodePoint   = points[1];

  308.         // TODO: Optimize KWeight - Would lookup table help?
  309.         switch (nodePoint) {
  310.         case PositionAlign.TL:
  311.             xy = [nodeRegion.left, nodeRegion.top];
  312.             break;

  313.         case PositionAlign.TR:
  314.             xy = [nodeRegion.right, nodeRegion.top];
  315.             break;

  316.         case PositionAlign.BL:
  317.             xy = [nodeRegion.left, nodeRegion.bottom];
  318.             break;

  319.         case PositionAlign.BR:
  320.             xy = [nodeRegion.right, nodeRegion.bottom];
  321.             break;

  322.         case PositionAlign.TC:
  323.             xy = [
  324.                 nodeRegion.left + Math.floor(nodeRegion.width / 2),
  325.                 nodeRegion.top
  326.             ];
  327.             break;

  328.         case PositionAlign.BC:
  329.             xy = [
  330.                 nodeRegion.left + Math.floor(nodeRegion.width / 2),
  331.                 nodeRegion.bottom
  332.             ];
  333.             break;

  334.         case PositionAlign.LC:
  335.             xy = [
  336.                 nodeRegion.left,
  337.                 nodeRegion.top + Math.floor(nodeRegion.height / 2)
  338.             ];
  339.             break;

  340.         case PositionAlign.RC:
  341.             xy = [
  342.                 nodeRegion.right,
  343.                 nodeRegion.top + Math.floor(nodeRegion.height / 2)
  344.             ];
  345.             break;

  346.         case PositionAlign.CC:
  347.             xy = [
  348.                 nodeRegion.left + Math.floor(nodeRegion.width / 2),
  349.                 nodeRegion.top + Math.floor(nodeRegion.height / 2)
  350.             ];
  351.             break;

  352.         default:
  353.             Y.log('align: Invalid Points Arguments', 'info',
  354.                 'widget-position-align');
  355.             break;

  356.         }

  357.         if (xy) {
  358.             this._doAlign(widgetPoint, xy[0], xy[1]);
  359.         }
  360.     },

  361.     /**
  362.     Attaches or detaches alignment-syncing event handlers based on the widget's
  363.     `visible` Attribute state.

  364.     @method _uiSetVisiblePosAlign
  365.     @param {Boolean} visible The current value of the widget's `visible`
  366.       Attribute.
  367.     @protected
  368.     **/
  369.     _uiSetVisiblePosAlign: function (visible) {
  370.         if (visible) {
  371.             this._attachPosAlignUIHandles();
  372.         } else {
  373.             this._detachPosAlignUIHandles();
  374.         }
  375.     },

  376.     /**
  377.     Attaches the alignment-syncing event handlers.

  378.     @method _attachPosAlignUIHandles
  379.     @protected
  380.     **/
  381.     _attachPosAlignUIHandles: function () {
  382.         if (this._posAlignUIHandles) {
  383.             // No-op if we have already setup the event handlers.
  384.             return;
  385.         }

  386.         var bb        = this.get(BOUNDING_BOX),
  387.             syncAlign = Y.bind(this._syncUIPosAlign, this),
  388.             handles   = [];

  389.         Y.Array.each(this.get(ALIGN_ON), function (o) {
  390.             var event = o.eventName,
  391.                 node  = Y.one(o.node) || bb;
  392.            
  393.             if (event) {
  394.                 handles.push(node.on(event, syncAlign));
  395.             }
  396.         });

  397.         this._posAlignUIHandles = handles;
  398.     },

  399.     /**
  400.     Detaches the alignment-syncing event handlers.

  401.     @method _detachPosAlignUIHandles
  402.     @protected
  403.     **/
  404.     _detachPosAlignUIHandles: function () {
  405.         var handles = this._posAlignUIHandles;
  406.         if (handles) {
  407.             new Y.EventHandle(handles).detach();
  408.             this._posAlignUIHandles = null;
  409.         }
  410.     },

  411.     // -- Private Methods ------------------------------------------------------

  412.     /**
  413.     Helper method, used to align the given point on the widget, with the XY page
  414.     coordinates provided.

  415.     @method _doAlign
  416.     @param {String} widgetPoint Supported point constant
  417.       (e.g. WidgetPositionAlign.TL)
  418.     @param {Number} x X page coordinate to align to.
  419.     @param {Number} y Y page coordinate to align to.
  420.     @private
  421.     **/
  422.     _doAlign: function (widgetPoint, x, y) {
  423.         var widgetNode = this._posNode,
  424.             xy;

  425.         switch (widgetPoint) {
  426.         case PositionAlign.TL:
  427.             xy = [x, y];
  428.             break;

  429.         case PositionAlign.TR:
  430.             xy = [
  431.                 x - widgetNode.get(OFFSET_WIDTH),
  432.                 y
  433.             ];
  434.             break;

  435.         case PositionAlign.BL:
  436.             xy = [
  437.                 x,
  438.                 y - widgetNode.get(OFFSET_HEIGHT)
  439.             ];
  440.             break;

  441.         case PositionAlign.BR:
  442.             xy = [
  443.                 x - widgetNode.get(OFFSET_WIDTH),
  444.                 y - widgetNode.get(OFFSET_HEIGHT)
  445.             ];
  446.             break;

  447.         case PositionAlign.TC:
  448.             xy = [
  449.                 x - (widgetNode.get(OFFSET_WIDTH) / 2),
  450.                 y
  451.             ];
  452.             break;

  453.         case PositionAlign.BC:
  454.             xy = [
  455.                 x - (widgetNode.get(OFFSET_WIDTH) / 2),
  456.                 y - widgetNode.get(OFFSET_HEIGHT)
  457.             ];
  458.             break;

  459.         case PositionAlign.LC:
  460.             xy = [
  461.                 x,
  462.                 y - (widgetNode.get(OFFSET_HEIGHT) / 2)
  463.             ];
  464.             break;

  465.         case PositionAlign.RC:
  466.             xy = [
  467.                 x - widgetNode.get(OFFSET_WIDTH),
  468.                 y - (widgetNode.get(OFFSET_HEIGHT) / 2)
  469.             ];
  470.             break;

  471.         case PositionAlign.CC:
  472.             xy = [
  473.                 x - (widgetNode.get(OFFSET_WIDTH) / 2),
  474.                 y - (widgetNode.get(OFFSET_HEIGHT) / 2)
  475.             ];
  476.             break;

  477.         default:
  478.             Y.log('align: Invalid Points Argument', 'info',
  479.                 'widget-position-align');
  480.             break;

  481.         }

  482.         if (xy) {
  483.             this.move(xy);
  484.         }
  485.     },

  486.     /**
  487.     Returns the region of the passed-in `Node`, or the viewport region if
  488.     calling with passing in a `Node`.

  489.     @method _getRegion
  490.     @param {Node} [node] The node to get the region of.
  491.     @return {Object} The node's region.
  492.     @private
  493.     **/
  494.     _getRegion: function (node) {
  495.         var nodeRegion;

  496.         if ( ! node) {
  497.             nodeRegion = this._posNode.get(VIEWPORT_REGION);
  498.         } else {
  499.             node = Y.Node.one(node);
  500.             if (node) {
  501.                 nodeRegion = node.get(REGION);
  502.             }
  503.         }

  504.         return nodeRegion;
  505.     },

  506.     // -- Protected Event Handlers ---------------------------------------------

  507.     /**
  508.     Handles `alignChange` events by updating the UI in response to `align`
  509.     Attribute changes.

  510.     @method _afterAlignChange
  511.     @param {EventFacade} e
  512.     @protected
  513.     **/
  514.     _afterAlignChange: function (e) {
  515.         var align = e.newVal;
  516.         if (align) {
  517.             this._uiSetAlign(align.node, align.points);              
  518.         }
  519.     },

  520.     /**
  521.     Handles `alignOnChange` events by updating the alignment-syncing event
  522.     handlers.

  523.     @method _afterAlignOnChange
  524.     @param {EventFacade} e
  525.     @protected
  526.     **/
  527.     _afterAlignOnChange: function(e) {
  528.         this._detachPosAlignUIHandles();

  529.         if (this.get(VISIBLE)) {
  530.             this._attachPosAlignUIHandles();
  531.         }
  532.     }
  533. };

  534. Y.WidgetPositionAlign = PositionAlign;

  535.