3
Copyright 2011 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('widget-position-constrain', function(Y) {
10
* Provides constrained xy positioning support for Widgets, through an extension.
12
* It builds on top of the widget-position module, to provide constrained positioning support.
14
* @module widget-position-constrain
16
var CONSTRAIN = "constrain",
17
CONSTRAIN_XYCHANGE = "constrain|xyChange",
18
CONSTRAIN_CHANGE = "constrainChange",
20
PREVENT_OVERLAP = "preventOverlap",
33
VIEWPORT_REGION = "viewportRegion",
39
* A widget extension, which can be used to add constrained xy positioning support to the base Widget class,
40
* through the <a href="Base.html#method_build">Base.build</a> method. This extension requires that
41
* the WidgetPosition extension be added to the Widget (before WidgetPositionConstrain, if part of the same
42
* extension list passed to Base.build).
44
* @class WidgetPositionConstrain
45
* @param {Object} User configuration object
47
function PositionConstrain(config) {
49
Y.error("WidgetPosition needs to be added to the Widget, before WidgetPositionConstrain is added");
51
Y.after(this._bindUIPosConstrained, this, BINDUI);
55
* Static property used to define the default attribute
56
* configuration introduced by WidgetPositionConstrain.
62
PositionConstrain.ATTRS = {
65
* @attribute constrain
66
* @type boolean | Node
68
* @description The node to constrain the widget's bounding box to, when setting xy. Can also be
69
* set to true, to constrain to the viewport.
73
setter: "_setConstrain"
77
* @attribute preventOverlap
79
* @description If set to true, and WidgetPositionAlign is also added to the Widget,
80
* constrained positioning will attempt to prevent the widget's bounding box from overlapping
81
* the element to which it has been aligned, by flipping the orientation of the alignment
82
* for corner based alignments
90
* @property _PREVENT_OVERLAP
94
* @description The set of positions for which to prevent
97
PREVENT_OVERLAP_MAP = PositionConstrain._PREVENT_OVERLAP = {
112
PositionConstrain.prototype = {
115
* Calculates the constrained positions for the XY positions provided, using
116
* the provided node argument is passed in. If no node value is passed in, the value of
117
* the "constrain" attribute is used.
119
* @method getConstrainedXY
120
* @param {Array} xy The xy values to constrain
121
* @param {Node | boolean} node Optional. The node to constrain to, or true for the viewport
122
* @return {Array} The constrained xy values
124
getConstrainedXY : function(xy, node) {
125
node = node || this.get(CONSTRAIN);
127
var constrainingRegion = this._getRegion((node === true) ? null : node),
128
nodeRegion = this._posNode.get(REGION);
131
this._constrain(xy[0], X_COORD, nodeRegion, constrainingRegion),
132
this._constrain(xy[1], Y_COORD, nodeRegion, constrainingRegion)
137
* Constrains the widget's bounding box to a node (or the viewport). If xy or node are not
138
* passed in, the current position and the value of "constrain" will be used respectively.
140
* The widget's position will be changed to the constrained position.
143
* @param {Array} xy Optional. The xy values to constrain
144
* @param {Node | boolean} node Optional. The node to constrain to, or true for the viewport
146
constrain : function(xy, node) {
149
constraint = node || this.get(CONSTRAIN);
152
currentXY = xy || this.get(XY);
153
constrainedXY = this.getConstrainedXY(currentXY, constraint);
155
if (constrainedXY[0] !== currentXY[0] || constrainedXY[1] !== currentXY[1]) {
156
this.set(XY, constrainedXY, { constrained:true });
162
* The setter implementation for the "constrain" attribute.
164
* @method _setConstrain
166
* @param {Node | boolean} val The attribute value
168
_setConstrain : function(val) {
169
return (val === true) ? val : Node.one(val);
173
* The method which performs the actual constrain calculations for a given axis ("x" or "y") based
174
* on the regions provided.
179
* @param {Number} val The value to constrain
180
* @param {String} axis The axis to use for constrainment
181
* @param {Region} nodeRegion The region of the node to constrain
182
* @param {Region} constrainingRegion The region of the node (or viewport) to constrain to
184
* @return {Number} The constrained value
186
_constrain: function(val, axis, nodeRegion, constrainingRegion) {
187
if (constrainingRegion) {
189
if (this.get(PREVENT_OVERLAP)) {
190
val = this._preventOverlap(val, axis, nodeRegion, constrainingRegion);
193
var x = (axis == X_COORD),
195
regionSize = (x) ? constrainingRegion.width : constrainingRegion.height,
196
nodeSize = (x) ? nodeRegion.width : nodeRegion.height,
197
minConstraint = (x) ? constrainingRegion.left : constrainingRegion.top,
198
maxConstraint = (x) ? constrainingRegion.right - nodeSize : constrainingRegion.bottom - nodeSize;
200
if (val < minConstraint || val > maxConstraint) {
201
if (nodeSize < regionSize) {
202
if (val < minConstraint) {
204
} else if (val > maxConstraint) {
217
* The method which performs the preventOverlap calculations for a given axis ("x" or "y") based
218
* on the value and regions provided.
220
* @method _preventOverlap
223
* @param {Number} val The value being constrain
224
* @param {String} axis The axis to being constrained
225
* @param {Region} nodeRegion The region of the node being constrained
226
* @param {Region} constrainingRegion The region of the node (or viewport) we need to constrain to
228
* @return {Number} The constrained value
230
_preventOverlap : function(val, axis, nodeRegion, constrainingRegion) {
232
var align = this.get(ALIGN),
233
x = (axis === X_COORD),
241
if (align && align.points && PREVENT_OVERLAP_MAP[axis][align.points.join(EMPTY_STR)]) {
243
alignRegion = this._getRegion(align.node);
246
nodeSize = (x) ? nodeRegion.width : nodeRegion.height;
247
nearEdge = (x) ? alignRegion.left : alignRegion.top;
248
farEdge = (x) ? alignRegion.right : alignRegion.bottom;
249
spaceOnNearSide = (x) ? alignRegion.left - constrainingRegion.left : alignRegion.top - constrainingRegion.top;
250
spaceOnFarSide = (x) ? constrainingRegion.right - alignRegion.right : constrainingRegion.bottom - alignRegion.bottom;
253
if (val > nearEdge) {
254
if (spaceOnFarSide < nodeSize && spaceOnNearSide > nodeSize) {
255
val = nearEdge - nodeSize;
258
if (spaceOnNearSide < nodeSize && spaceOnFarSide > nodeSize) {
268
* Binds event listeners responsible for updating the UI state in response to
269
* Widget constrained positioning related state changes.
271
* This method is invoked after bindUI is invoked for the Widget class
272
* using YUI's aop infrastructure.
275
* @method _bindUIPosConstrained
278
_bindUIPosConstrained : function() {
279
this.after(CONSTRAIN_CHANGE, this._afterConstrainChange);
280
this._enableConstraints(this.get(CONSTRAIN));
284
* After change listener for the "constrain" attribute, responsible
285
* for updating the UI, in response to attribute changes.
287
* @method _afterConstrainChange
289
* @param {EventFacade} e The event facade
291
_afterConstrainChange : function(e) {
292
this._enableConstraints(e.newVal);
296
* Updates the UI if enabling constraints, and sets up the xyChange event listeners
297
* to constrain whenever the widget is moved. Disabling constraints removes the listeners.
299
* @method enable or disable constraints listeners
301
* @param {boolean} enable Enable or disable constraints
303
_enableConstraints : function(enable) {
306
this._cxyHandle = this._cxyHandle || this.on(CONSTRAIN_XYCHANGE, this._constrainOnXYChange);
307
} else if (this._cxyHandle) {
308
this._cxyHandle.detach();
309
this._cxyHandle = null;
314
* The on change listener for the "xy" attribute. Modifies the event facade's
315
* newVal property with the constrained XY value.
317
* @method _constrainOnXYChange
319
* @param {EventFacade} e The event facade for the attribute change
321
_constrainOnXYChange : function(e) {
322
if (!e.constrained) {
323
e.newVal = this.getConstrainedXY(e.newVal);
328
* Utility method to normalize region retrieval from a node instance,
329
* or the viewport, if no node is provided.
333
* @param {Node} node Optional.
335
_getRegion : function(node) {
338
region = this._posNode.get(VIEWPORT_REGION);
340
node = Node.one(node);
342
region = node.get(REGION);
349
Y.WidgetPositionConstrain = PositionConstrain;
352
}, '3.4.1' ,{requires:['widget-position']});