- /**
- Provides extended/advanced XY positioning support for Widgets, through an
- extension.
-
- It builds on top of the `widget-position` module, to provide alignment and
- centering support. Future releases aim to add constrained and fixed positioning
- support.
-
- @module widget-position-align
- **/
- var Lang = Y.Lang,
-
- ALIGN = 'align',
- ALIGN_ON = 'alignOn',
-
- VISIBLE = 'visible',
- BOUNDING_BOX = 'boundingBox',
-
- OFFSET_WIDTH = 'offsetWidth',
- OFFSET_HEIGHT = 'offsetHeight',
- REGION = 'region',
- VIEWPORT_REGION = 'viewportRegion';
-
- /**
- Widget extension, which can be used to add extended XY positioning support to
- the base Widget class, through the `Base.create` method.
-
- **Note:** This extension requires that the `WidgetPosition` extension be added
- to the Widget (before `WidgetPositionAlign`, if part of the same extension list
- passed to `Base.build`).
-
- @class WidgetPositionAlign
- @param {Object} config User configuration object.
- @constructor
- **/
- function PositionAlign (config) {}
-
- PositionAlign.ATTRS = {
-
- /**
- The alignment configuration for this widget.
-
- The `align` attribute is used to align a reference point on the widget, with
- the reference point on another `Node`, or the viewport. The object which
- `align` expects has the following properties:
-
- * __`node`__: The `Node` to which the widget is to be aligned. If set to
- `null`, or not provided, the widget is aligned to the viewport.
-
- * __`points`__: A two element Array, defining the two points on the widget
- and `Node`/viewport which are to be aligned. The first element is the
- point on the widget, and the second element is the point on the
- `Node`/viewport. Supported alignment points are defined as static
- properties on `WidgetPositionAlign`.
-
- @example Aligns the top-right corner of the widget with the top-left corner
- of the viewport:
-
- myWidget.set('align', {
- points: [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.TL]
- });
-
- @attribute align
- @type Object
- @default null
- **/
- align: {
- value: null
- },
-
- /**
- A convenience Attribute, which can be used as a shortcut for the `align`
- Attribute.
-
- If set to `true`, the widget is centered in the viewport. If set to a `Node`
- reference or valid selector String, the widget will be centered within the
- `Node`. If set to `false`, no center positioning is applied.
-
- @attribute centered
- @type Boolean|Node
- @default false
- **/
- centered: {
- setter : '_setAlignCenter',
- lazyAdd:false,
- value :false
- },
-
- /**
- An Array of Objects corresponding to the `Node`s and events that will cause
- the alignment of this widget to be synced to the DOM.
-
- The `alignOn` Attribute is expected to be an Array of Objects with the
- following properties:
-
- * __`eventName`__: The String event name to listen for.
-
- * __`node`__: The optional `Node` that will fire the event, it can be a
- `Node` reference or a selector String. This will default to the widget's
- `boundingBox`.
-
- @example Sync this widget's alignment on window resize:
-
- myWidget.set('alignOn', [
- {
- node : Y.one('win'),
- eventName: 'resize'
- }
- ]);
-
- @attribute alignOn
- @type Array
- @default []
- **/
- alignOn: {
- value : [],
- validator: Y.Lang.isArray
- }
- };
-
- /**
- Constant used to specify the top-left corner for alignment
-
- @property TL
- @type String
- @value 'tl'
- @static
- **/
- PositionAlign.TL = 'tl';
-
- /**
- Constant used to specify the top-right corner for alignment
-
- @property TR
- @type String
- @value 'tr'
- @static
- **/
- PositionAlign.TR = 'tr';
-
- /**
- Constant used to specify the bottom-left corner for alignment
-
- @property BL
- @type String
- @value 'bl'
- @static
- **/
- PositionAlign.BL = 'bl';
-
- /**
- Constant used to specify the bottom-right corner for alignment
-
- @property BR
- @type String
- @value 'br'
- @static
- **/
- PositionAlign.BR = 'br';
-
- /**
- Constant used to specify the top edge-center point for alignment
-
- @property TC
- @type String
- @value 'tc'
- @static
- **/
- PositionAlign.TC = 'tc';
-
- /**
- Constant used to specify the right edge, center point for alignment
-
- @property RC
- @type String
- @value 'rc'
- @static
- **/
- PositionAlign.RC = 'rc';
-
- /**
- Constant used to specify the bottom edge, center point for alignment
-
- @property BC
- @type String
- @value 'bc'
- @static
- **/
- PositionAlign.BC = 'bc';
-
- /**
- Constant used to specify the left edge, center point for alignment
-
- @property LC
- @type String
- @value 'lc'
- @static
- **/
- PositionAlign.LC = 'lc';
-
- /**
- Constant used to specify the center of widget/node/viewport for alignment
-
- @property CC
- @type String
- @value 'cc'
- @static
- */
- PositionAlign.CC = 'cc';
-
- PositionAlign.prototype = {
- // -- Protected Properties -------------------------------------------------
-
-
- initializer : function() {
- if (!this._posNode) {
- Y.error('WidgetPosition needs to be added to the Widget, ' +
- 'before WidgetPositionAlign is added');
- }
-
- Y.after(this._bindUIPosAlign, this, 'bindUI');
- Y.after(this._syncUIPosAlign, this, 'syncUI');
- },
-
- /**
- Holds the alignment-syncing event handles.
-
- @property _posAlignUIHandles
- @type Array
- @default null
- @protected
- **/
- _posAlignUIHandles: null,
-
- // -- Lifecycle Methods ----------------------------------------------------
-
- destructor: function () {
- this._detachPosAlignUIHandles();
- },
-
- /**
- Bind event listeners responsible for updating the UI state in response to
- the widget's position-align related state changes.
-
- This method is invoked after `bindUI` has been invoked for the `Widget`
- class using the AOP infrastructure.
-
- @method _bindUIPosAlign
- @protected
- **/
- _bindUIPosAlign: function () {
- this.after('alignChange', this._afterAlignChange);
- this.after('alignOnChange', this._afterAlignOnChange);
- this.after('visibleChange', this._syncUIPosAlign);
- },
-
- /**
- Synchronizes the current `align` Attribute value to the DOM.
-
- This method is invoked after `syncUI` has been invoked for the `Widget`
- class using the AOP infrastructure.
-
- @method _syncUIPosAlign
- @protected
- **/
- _syncUIPosAlign: function () {
- var align = this.get(ALIGN);
-
- this._uiSetVisiblePosAlign(this.get(VISIBLE));
-
- if (align) {
- this._uiSetAlign(align.node, align.points);
- }
- },
-
- // -- Public Methods -------------------------------------------------------
-
- /**
- Aligns this widget to the provided `Node` (or viewport) using the provided
- points. This method can be invoked with no arguments which will cause the
- widget's current `align` Attribute value to be synced to the DOM.
-
- @example Aligning to the top-left corner of the `<body>`:
-
- myWidget.align('body',
- [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.TR]);
-
- @method align
- @param {Node|String|null} [node] A reference (or selector String) for the
- `Node` which with the widget is to be aligned. If null is passed in, the
- widget will be aligned with the viewport.
- @param {Array[2]} [points] A two item array specifying the points on the
- widget and `Node`/viewport which will to be aligned. The first entry is
- the point on the widget, and the second entry is the point on the
- `Node`/viewport. Valid point references are defined as static constants on
- the `WidgetPositionAlign` extension.
- @chainable
- **/
- align: function (node, points) {
- if (arguments.length) {
- // Set the `align` Attribute.
- this.set(ALIGN, {
- node : node,
- points: points
- });
- } else {
- // Sync the current `align` Attribute value to the DOM.
- this._syncUIPosAlign();
- }
-
- return this;
- },
-
- /**
- Centers the widget in the viewport, or if a `Node` is passed in, it will
- be centered to that `Node`.
-
- @method centered
- @param {Node|String} [node] A `Node` reference or selector String defining
- the `Node` which the widget should be centered. If a `Node` is not passed
- in, then the widget will be centered to the viewport.
- @chainable
- **/
- centered: function (node) {
- return this.align(node, [PositionAlign.CC, PositionAlign.CC]);
- },
-
- // -- Protected Methods ----------------------------------------------------
-
- /**
- Default setter for `center` Attribute changes. Sets up the appropriate
- value, and passes it through the to the align attribute.
-
- @method _setAlignCenter
- @param {Boolean|Node} val The Attribute value being set.
- @return {Boolean|Node} the value passed in.
- @protected
- **/
- _setAlignCenter: function (val) {
- if (val) {
- this.set(ALIGN, {
- node : val === true ? null : val,
- points: [PositionAlign.CC, PositionAlign.CC]
- });
- }
-
- return val;
- },
-
- /**
- Updates the UI to reflect the `align` value passed in.
-
- **Note:** See the `align` Attribute documentation, for the Object structure
- expected.
-
- @method _uiSetAlign
- @param {Node|String|null} [node] The node to align to, or null to indicate
- the viewport.
- @param {Array} points The alignment points.
- @protected
- **/
- _uiSetAlign: function (node, points) {
- if ( ! Lang.isArray(points) || points.length !== 2) {
- Y.error('align: Invalid Points Arguments');
- return;
- }
-
- var nodeRegion = this._getRegion(node),
- widgetPoint, nodePoint, xy;
-
- if ( ! nodeRegion) {
- // No-op, nothing to align to.
- return;
- }
-
- widgetPoint = points[0];
- nodePoint = points[1];
-
- // TODO: Optimize KWeight - Would lookup table help?
- switch (nodePoint) {
- case PositionAlign.TL:
- xy = [nodeRegion.left, nodeRegion.top];
- break;
-
- case PositionAlign.TR:
- xy = [nodeRegion.right, nodeRegion.top];
- break;
-
- case PositionAlign.BL:
- xy = [nodeRegion.left, nodeRegion.bottom];
- break;
-
- case PositionAlign.BR:
- xy = [nodeRegion.right, nodeRegion.bottom];
- break;
-
- case PositionAlign.TC:
- xy = [
- nodeRegion.left + Math.floor(nodeRegion.width / 2),
- nodeRegion.top
- ];
- break;
-
- case PositionAlign.BC:
- xy = [
- nodeRegion.left + Math.floor(nodeRegion.width / 2),
- nodeRegion.bottom
- ];
- break;
-
- case PositionAlign.LC:
- xy = [
- nodeRegion.left,
- nodeRegion.top + Math.floor(nodeRegion.height / 2)
- ];
- break;
-
- case PositionAlign.RC:
- xy = [
- nodeRegion.right,
- nodeRegion.top + Math.floor(nodeRegion.height / 2)
- ];
- break;
-
- case PositionAlign.CC:
- xy = [
- nodeRegion.left + Math.floor(nodeRegion.width / 2),
- nodeRegion.top + Math.floor(nodeRegion.height / 2)
- ];
- break;
-
- default:
- Y.log('align: Invalid Points Arguments', 'info',
- 'widget-position-align');
- break;
-
- }
-
- if (xy) {
- this._doAlign(widgetPoint, xy[0], xy[1]);
- }
- },
-
- /**
- Attaches or detaches alignment-syncing event handlers based on the widget's
- `visible` Attribute state.
-
- @method _uiSetVisiblePosAlign
- @param {Boolean} visible The current value of the widget's `visible`
- Attribute.
- @protected
- **/
- _uiSetVisiblePosAlign: function (visible) {
- if (visible) {
- this._attachPosAlignUIHandles();
- } else {
- this._detachPosAlignUIHandles();
- }
- },
-
- /**
- Attaches the alignment-syncing event handlers.
-
- @method _attachPosAlignUIHandles
- @protected
- **/
- _attachPosAlignUIHandles: function () {
- if (this._posAlignUIHandles) {
- // No-op if we have already setup the event handlers.
- return;
- }
-
- var bb = this.get(BOUNDING_BOX),
- syncAlign = Y.bind(this._syncUIPosAlign, this),
- handles = [];
-
- Y.Array.each(this.get(ALIGN_ON), function (o) {
- var event = o.eventName,
- node = Y.one(o.node) || bb;
-
- if (event) {
- handles.push(node.on(event, syncAlign));
- }
- });
-
- this._posAlignUIHandles = handles;
- },
-
- /**
- Detaches the alignment-syncing event handlers.
-
- @method _detachPosAlignUIHandles
- @protected
- **/
- _detachPosAlignUIHandles: function () {
- var handles = this._posAlignUIHandles;
- if (handles) {
- new Y.EventHandle(handles).detach();
- this._posAlignUIHandles = null;
- }
- },
-
- // -- Private Methods ------------------------------------------------------
-
- /**
- Helper method, used to align the given point on the widget, with the XY page
- coordinates provided.
-
- @method _doAlign
- @param {String} widgetPoint Supported point constant
- (e.g. WidgetPositionAlign.TL)
- @param {Number} x X page coordinate to align to.
- @param {Number} y Y page coordinate to align to.
- @private
- **/
- _doAlign: function (widgetPoint, x, y) {
- var widgetNode = this._posNode,
- xy;
-
- switch (widgetPoint) {
- case PositionAlign.TL:
- xy = [x, y];
- break;
-
- case PositionAlign.TR:
- xy = [
- x - widgetNode.get(OFFSET_WIDTH),
- y
- ];
- break;
-
- case PositionAlign.BL:
- xy = [
- x,
- y - widgetNode.get(OFFSET_HEIGHT)
- ];
- break;
-
- case PositionAlign.BR:
- xy = [
- x - widgetNode.get(OFFSET_WIDTH),
- y - widgetNode.get(OFFSET_HEIGHT)
- ];
- break;
-
- case PositionAlign.TC:
- xy = [
- x - (widgetNode.get(OFFSET_WIDTH) / 2),
- y
- ];
- break;
-
- case PositionAlign.BC:
- xy = [
- x - (widgetNode.get(OFFSET_WIDTH) / 2),
- y - widgetNode.get(OFFSET_HEIGHT)
- ];
- break;
-
- case PositionAlign.LC:
- xy = [
- x,
- y - (widgetNode.get(OFFSET_HEIGHT) / 2)
- ];
- break;
-
- case PositionAlign.RC:
- xy = [
- x - widgetNode.get(OFFSET_WIDTH),
- y - (widgetNode.get(OFFSET_HEIGHT) / 2)
- ];
- break;
-
- case PositionAlign.CC:
- xy = [
- x - (widgetNode.get(OFFSET_WIDTH) / 2),
- y - (widgetNode.get(OFFSET_HEIGHT) / 2)
- ];
- break;
-
- default:
- Y.log('align: Invalid Points Argument', 'info',
- 'widget-position-align');
- break;
-
- }
-
- if (xy) {
- this.move(xy);
- }
- },
-
- /**
- Returns the region of the passed-in `Node`, or the viewport region if
- calling with passing in a `Node`.
-
- @method _getRegion
- @param {Node} [node] The node to get the region of.
- @return {Object} The node's region.
- @private
- **/
- _getRegion: function (node) {
- var nodeRegion;
-
- if ( ! node) {
- nodeRegion = this._posNode.get(VIEWPORT_REGION);
- } else {
- node = Y.Node.one(node);
- if (node) {
- nodeRegion = node.get(REGION);
- }
- }
-
- return nodeRegion;
- },
-
- // -- Protected Event Handlers ---------------------------------------------
-
- /**
- Handles `alignChange` events by updating the UI in response to `align`
- Attribute changes.
-
- @method _afterAlignChange
- @param {EventFacade} e
- @protected
- **/
- _afterAlignChange: function (e) {
- var align = e.newVal;
- if (align) {
- this._uiSetAlign(align.node, align.points);
- }
- },
-
- /**
- Handles `alignOnChange` events by updating the alignment-syncing event
- handlers.
-
- @method _afterAlignOnChange
- @param {EventFacade} e
- @protected
- **/
- _afterAlignOnChange: function(e) {
- this._detachPosAlignUIHandles();
-
- if (this.get(VISIBLE)) {
- this._attachPosAlignUIHandles();
- }
- }
- };
-
- Y.WidgetPositionAlign = PositionAlign;
-
-