|
1 /* |
|
2 YUI 3.10.3 (build 2fb5187) |
|
3 Copyright 2013 Yahoo! Inc. All rights reserved. |
|
4 Licensed under the BSD License. |
|
5 http://yuilibrary.com/license/ |
|
6 */ |
|
7 |
|
8 YUI.add('widget-position-constrain', function (Y, NAME) { |
|
9 |
|
10 /** |
|
11 * Provides constrained xy positioning support for Widgets, through an extension. |
|
12 * |
|
13 * It builds on top of the widget-position module, to provide constrained positioning support. |
|
14 * |
|
15 * @module widget-position-constrain |
|
16 */ |
|
17 var CONSTRAIN = "constrain", |
|
18 CONSTRAIN_XYCHANGE = "constrain|xyChange", |
|
19 CONSTRAIN_CHANGE = "constrainChange", |
|
20 |
|
21 PREVENT_OVERLAP = "preventOverlap", |
|
22 ALIGN = "align", |
|
23 |
|
24 EMPTY_STR = "", |
|
25 |
|
26 BINDUI = "bindUI", |
|
27 |
|
28 XY = "xy", |
|
29 X_COORD = "x", |
|
30 Y_COORD = "y", |
|
31 |
|
32 Node = Y.Node, |
|
33 |
|
34 VIEWPORT_REGION = "viewportRegion", |
|
35 REGION = "region", |
|
36 |
|
37 PREVENT_OVERLAP_MAP; |
|
38 |
|
39 /** |
|
40 * A widget extension, which can be used to add constrained xy positioning support to the base Widget class, |
|
41 * through the <a href="Base.html#method_build">Base.build</a> method. This extension requires that |
|
42 * the WidgetPosition extension be added to the Widget (before WidgetPositionConstrain, if part of the same |
|
43 * extension list passed to Base.build). |
|
44 * |
|
45 * @class WidgetPositionConstrain |
|
46 * @param {Object} User configuration object |
|
47 */ |
|
48 function PositionConstrain(config) { |
|
49 if (!this._posNode) { |
|
50 Y.error("WidgetPosition needs to be added to the Widget, before WidgetPositionConstrain is added"); |
|
51 } |
|
52 Y.after(this._bindUIPosConstrained, this, BINDUI); |
|
53 } |
|
54 |
|
55 /** |
|
56 * Static property used to define the default attribute |
|
57 * configuration introduced by WidgetPositionConstrain. |
|
58 * |
|
59 * @property ATTRS |
|
60 * @type Object |
|
61 * @static |
|
62 */ |
|
63 PositionConstrain.ATTRS = { |
|
64 |
|
65 /** |
|
66 * @attribute constrain |
|
67 * @type boolean | Node |
|
68 * @default null |
|
69 * @description The node to constrain the widget's bounding box to, when setting xy. Can also be |
|
70 * set to true, to constrain to the viewport. |
|
71 */ |
|
72 constrain : { |
|
73 value: null, |
|
74 setter: "_setConstrain" |
|
75 }, |
|
76 |
|
77 /** |
|
78 * @attribute preventOverlap |
|
79 * @type boolean |
|
80 * @description If set to true, and WidgetPositionAlign is also added to the Widget, |
|
81 * constrained positioning will attempt to prevent the widget's bounding box from overlapping |
|
82 * the element to which it has been aligned, by flipping the orientation of the alignment |
|
83 * for corner based alignments |
|
84 */ |
|
85 preventOverlap : { |
|
86 value:false |
|
87 } |
|
88 }; |
|
89 |
|
90 /** |
|
91 * @property _PREVENT_OVERLAP |
|
92 * @static |
|
93 * @protected |
|
94 * @type Object |
|
95 * @description The set of positions for which to prevent |
|
96 * overlap. |
|
97 */ |
|
98 PREVENT_OVERLAP_MAP = PositionConstrain._PREVENT_OVERLAP = { |
|
99 x: { |
|
100 "tltr": 1, |
|
101 "blbr": 1, |
|
102 "brbl": 1, |
|
103 "trtl": 1 |
|
104 }, |
|
105 y : { |
|
106 "trbr": 1, |
|
107 "tlbl": 1, |
|
108 "bltl": 1, |
|
109 "brtr": 1 |
|
110 } |
|
111 }; |
|
112 |
|
113 PositionConstrain.prototype = { |
|
114 |
|
115 /** |
|
116 * Calculates the constrained positions for the XY positions provided, using |
|
117 * the provided node argument is passed in. If no node value is passed in, the value of |
|
118 * the "constrain" attribute is used. |
|
119 * |
|
120 * @method getConstrainedXY |
|
121 * @param {Array} xy The xy values to constrain |
|
122 * @param {Node | boolean} node Optional. The node to constrain to, or true for the viewport |
|
123 * @return {Array} The constrained xy values |
|
124 */ |
|
125 getConstrainedXY : function(xy, node) { |
|
126 node = node || this.get(CONSTRAIN); |
|
127 |
|
128 var constrainingRegion = this._getRegion((node === true) ? null : node), |
|
129 nodeRegion = this._posNode.get(REGION); |
|
130 |
|
131 return [ |
|
132 this._constrain(xy[0], X_COORD, nodeRegion, constrainingRegion), |
|
133 this._constrain(xy[1], Y_COORD, nodeRegion, constrainingRegion) |
|
134 ]; |
|
135 }, |
|
136 |
|
137 /** |
|
138 * Constrains the widget's bounding box to a node (or the viewport). If xy or node are not |
|
139 * passed in, the current position and the value of "constrain" will be used respectively. |
|
140 * |
|
141 * The widget's position will be changed to the constrained position. |
|
142 * |
|
143 * @method constrain |
|
144 * @param {Array} xy Optional. The xy values to constrain |
|
145 * @param {Node | boolean} node Optional. The node to constrain to, or true for the viewport |
|
146 */ |
|
147 constrain : function(xy, node) { |
|
148 var currentXY, |
|
149 constrainedXY, |
|
150 constraint = node || this.get(CONSTRAIN); |
|
151 |
|
152 if (constraint) { |
|
153 currentXY = xy || this.get(XY); |
|
154 constrainedXY = this.getConstrainedXY(currentXY, constraint); |
|
155 |
|
156 if (constrainedXY[0] !== currentXY[0] || constrainedXY[1] !== currentXY[1]) { |
|
157 this.set(XY, constrainedXY, { constrained:true }); |
|
158 } |
|
159 } |
|
160 }, |
|
161 |
|
162 /** |
|
163 * The setter implementation for the "constrain" attribute. |
|
164 * |
|
165 * @method _setConstrain |
|
166 * @protected |
|
167 * @param {Node | boolean} val The attribute value |
|
168 */ |
|
169 _setConstrain : function(val) { |
|
170 return (val === true) ? val : Node.one(val); |
|
171 }, |
|
172 |
|
173 /** |
|
174 * The method which performs the actual constrain calculations for a given axis ("x" or "y") based |
|
175 * on the regions provided. |
|
176 * |
|
177 * @method _constrain |
|
178 * @protected |
|
179 * |
|
180 * @param {Number} val The value to constrain |
|
181 * @param {String} axis The axis to use for constrainment |
|
182 * @param {Region} nodeRegion The region of the node to constrain |
|
183 * @param {Region} constrainingRegion The region of the node (or viewport) to constrain to |
|
184 * |
|
185 * @return {Number} The constrained value |
|
186 */ |
|
187 _constrain: function(val, axis, nodeRegion, constrainingRegion) { |
|
188 if (constrainingRegion) { |
|
189 |
|
190 if (this.get(PREVENT_OVERLAP)) { |
|
191 val = this._preventOverlap(val, axis, nodeRegion, constrainingRegion); |
|
192 } |
|
193 |
|
194 var x = (axis == X_COORD), |
|
195 |
|
196 regionSize = (x) ? constrainingRegion.width : constrainingRegion.height, |
|
197 nodeSize = (x) ? nodeRegion.width : nodeRegion.height, |
|
198 minConstraint = (x) ? constrainingRegion.left : constrainingRegion.top, |
|
199 maxConstraint = (x) ? constrainingRegion.right - nodeSize : constrainingRegion.bottom - nodeSize; |
|
200 |
|
201 if (val < minConstraint || val > maxConstraint) { |
|
202 if (nodeSize < regionSize) { |
|
203 if (val < minConstraint) { |
|
204 val = minConstraint; |
|
205 } else if (val > maxConstraint) { |
|
206 val = maxConstraint; |
|
207 } |
|
208 } else { |
|
209 val = minConstraint; |
|
210 } |
|
211 } |
|
212 } |
|
213 |
|
214 return val; |
|
215 }, |
|
216 |
|
217 /** |
|
218 * The method which performs the preventOverlap calculations for a given axis ("x" or "y") based |
|
219 * on the value and regions provided. |
|
220 * |
|
221 * @method _preventOverlap |
|
222 * @protected |
|
223 * |
|
224 * @param {Number} val The value being constrain |
|
225 * @param {String} axis The axis to being constrained |
|
226 * @param {Region} nodeRegion The region of the node being constrained |
|
227 * @param {Region} constrainingRegion The region of the node (or viewport) we need to constrain to |
|
228 * |
|
229 * @return {Number} The constrained value |
|
230 */ |
|
231 _preventOverlap : function(val, axis, nodeRegion, constrainingRegion) { |
|
232 |
|
233 var align = this.get(ALIGN), |
|
234 x = (axis === X_COORD), |
|
235 nodeSize, |
|
236 alignRegion, |
|
237 nearEdge, |
|
238 farEdge, |
|
239 spaceOnNearSide, |
|
240 spaceOnFarSide; |
|
241 |
|
242 if (align && align.points && PREVENT_OVERLAP_MAP[axis][align.points.join(EMPTY_STR)]) { |
|
243 |
|
244 alignRegion = this._getRegion(align.node); |
|
245 |
|
246 if (alignRegion) { |
|
247 nodeSize = (x) ? nodeRegion.width : nodeRegion.height; |
|
248 nearEdge = (x) ? alignRegion.left : alignRegion.top; |
|
249 farEdge = (x) ? alignRegion.right : alignRegion.bottom; |
|
250 spaceOnNearSide = (x) ? alignRegion.left - constrainingRegion.left : alignRegion.top - constrainingRegion.top; |
|
251 spaceOnFarSide = (x) ? constrainingRegion.right - alignRegion.right : constrainingRegion.bottom - alignRegion.bottom; |
|
252 } |
|
253 |
|
254 if (val > nearEdge) { |
|
255 if (spaceOnFarSide < nodeSize && spaceOnNearSide > nodeSize) { |
|
256 val = nearEdge - nodeSize; |
|
257 } |
|
258 } else { |
|
259 if (spaceOnNearSide < nodeSize && spaceOnFarSide > nodeSize) { |
|
260 val = farEdge; |
|
261 } |
|
262 } |
|
263 } |
|
264 |
|
265 return val; |
|
266 }, |
|
267 |
|
268 /** |
|
269 * Binds event listeners responsible for updating the UI state in response to |
|
270 * Widget constrained positioning related state changes. |
|
271 * <p> |
|
272 * This method is invoked after bindUI is invoked for the Widget class |
|
273 * using YUI's aop infrastructure. |
|
274 * </p> |
|
275 * |
|
276 * @method _bindUIPosConstrained |
|
277 * @protected |
|
278 */ |
|
279 _bindUIPosConstrained : function() { |
|
280 this.after(CONSTRAIN_CHANGE, this._afterConstrainChange); |
|
281 this._enableConstraints(this.get(CONSTRAIN)); |
|
282 }, |
|
283 |
|
284 /** |
|
285 * After change listener for the "constrain" attribute, responsible |
|
286 * for updating the UI, in response to attribute changes. |
|
287 * |
|
288 * @method _afterConstrainChange |
|
289 * @protected |
|
290 * @param {EventFacade} e The event facade |
|
291 */ |
|
292 _afterConstrainChange : function(e) { |
|
293 this._enableConstraints(e.newVal); |
|
294 }, |
|
295 |
|
296 /** |
|
297 * Updates the UI if enabling constraints, and sets up the xyChange event listeners |
|
298 * to constrain whenever the widget is moved. Disabling constraints removes the listeners. |
|
299 * |
|
300 * @method enable or disable constraints listeners |
|
301 * @private |
|
302 * @param {boolean} enable Enable or disable constraints |
|
303 */ |
|
304 _enableConstraints : function(enable) { |
|
305 if (enable) { |
|
306 this.constrain(); |
|
307 this._cxyHandle = this._cxyHandle || this.on(CONSTRAIN_XYCHANGE, this._constrainOnXYChange); |
|
308 } else if (this._cxyHandle) { |
|
309 this._cxyHandle.detach(); |
|
310 this._cxyHandle = null; |
|
311 } |
|
312 }, |
|
313 |
|
314 /** |
|
315 * The on change listener for the "xy" attribute. Modifies the event facade's |
|
316 * newVal property with the constrained XY value. |
|
317 * |
|
318 * @method _constrainOnXYChange |
|
319 * @protected |
|
320 * @param {EventFacade} e The event facade for the attribute change |
|
321 */ |
|
322 _constrainOnXYChange : function(e) { |
|
323 if (!e.constrained) { |
|
324 e.newVal = this.getConstrainedXY(e.newVal); |
|
325 } |
|
326 }, |
|
327 |
|
328 /** |
|
329 * Utility method to normalize region retrieval from a node instance, |
|
330 * or the viewport, if no node is provided. |
|
331 * |
|
332 * @method _getRegion |
|
333 * @private |
|
334 * @param {Node} node Optional. |
|
335 */ |
|
336 _getRegion : function(node) { |
|
337 var region; |
|
338 if (!node) { |
|
339 region = this._posNode.get(VIEWPORT_REGION); |
|
340 } else { |
|
341 node = Node.one(node); |
|
342 if (node) { |
|
343 region = node.get(REGION); |
|
344 } |
|
345 } |
|
346 return region; |
|
347 } |
|
348 }; |
|
349 |
|
350 Y.WidgetPositionConstrain = PositionConstrain; |
|
351 |
|
352 |
|
353 }, '3.10.3', {"requires": ["widget-position"]}); |