|
0
|
1 |
/* |
|
|
2 |
Copyright (c) 2009, Yahoo! Inc. All rights reserved. |
|
|
3 |
Code licensed under the BSD License: |
|
|
4 |
http://developer.yahoo.net/yui/license.txt |
|
|
5 |
version: 3.0.0b1 |
|
|
6 |
build: 1163 |
|
|
7 |
*/ |
|
|
8 |
YUI.add('dd-constrain', function(Y) { |
|
|
9 |
|
|
|
10 |
|
|
|
11 |
/** |
|
|
12 |
* The Drag & Drop Utility allows you to create a draggable interface efficiently, buffering you from browser-level abnormalities and enabling you to focus on the interesting logic surrounding your particular implementation. This component enables you to create a variety of standard draggable objects with just a few lines of code and then, using its extensive API, add your own specific implementation logic. |
|
|
13 |
* @module dd |
|
|
14 |
* @submodule dd-constrain |
|
|
15 |
*/ |
|
|
16 |
/** |
|
|
17 |
* This is a plugin for the dd-drag module to add the constraining methods to it. It supports constraining to a renodenode or viewport. It anode* supports tick based moves and XY axis constraints. |
|
|
18 |
* @class DragConstrained |
|
|
19 |
* @extends Base |
|
|
20 |
* @constructor |
|
|
21 |
* @namespace Plugin |
|
|
22 |
*/ |
|
|
23 |
|
|
|
24 |
var DRAG_NODE = 'dragNode', |
|
|
25 |
OFFSET_HEIGHT = 'offsetHeight', |
|
|
26 |
OFFSET_WIDTH = 'offsetWidth', |
|
|
27 |
HOST = 'host', |
|
|
28 |
CON_2_REGION = 'constrain2region', |
|
|
29 |
CON_2_NODE = 'constrain2node', |
|
|
30 |
TICK_X_ARRAY = 'tickXArray', |
|
|
31 |
TICK_Y_ARRAY = 'tickYArray', |
|
|
32 |
DDM = Y.DD.DDM, |
|
|
33 |
TOP = 'top', |
|
|
34 |
RIGHT = 'right', |
|
|
35 |
BOTTOM = 'bottom', |
|
|
36 |
LEFT = 'left', |
|
|
37 |
proto = null; |
|
|
38 |
|
|
|
39 |
var C = function(config) { |
|
|
40 |
C.superclass.constructor.apply(this, arguments); |
|
|
41 |
}; |
|
|
42 |
|
|
|
43 |
C.NAME = 'DragConstrained'; |
|
|
44 |
/** |
|
|
45 |
* @property con |
|
|
46 |
* @description The Constrained instance will be placed on the Drag instance under the con namespace. |
|
|
47 |
* @type {String} |
|
|
48 |
*/ |
|
|
49 |
C.NS = 'con'; |
|
|
50 |
|
|
|
51 |
C.ATTRS = { |
|
|
52 |
host: { |
|
|
53 |
}, |
|
|
54 |
/** |
|
|
55 |
* @attribute stickX |
|
|
56 |
* @description Stick the drag movement to the X-Axis. Default: false |
|
|
57 |
* @type Boolean |
|
|
58 |
*/ |
|
|
59 |
stickX: { |
|
|
60 |
value: false |
|
|
61 |
}, |
|
|
62 |
/** |
|
|
63 |
* @attribute stickY |
|
|
64 |
* @description Stick the drag movement to the Y-Axis |
|
|
65 |
* @type Boolean |
|
|
66 |
*/ |
|
|
67 |
stickY: { |
|
|
68 |
value: false |
|
|
69 |
}, |
|
|
70 |
/** |
|
|
71 |
* @attribute tickX |
|
|
72 |
* @description The X tick offset the drag node should snap to on each drag move. False for no ticks. Default: false |
|
|
73 |
* @type Number/false |
|
|
74 |
*/ |
|
|
75 |
tickX: { |
|
|
76 |
value: false |
|
|
77 |
}, |
|
|
78 |
/** |
|
|
79 |
* @attribute tickY |
|
|
80 |
* @description The Y tick offset the drag node should snap to on each drag move. False for no ticks. Default: false |
|
|
81 |
* @type Number/false |
|
|
82 |
*/ |
|
|
83 |
tickY: { |
|
|
84 |
value: false |
|
|
85 |
}, |
|
|
86 |
/** |
|
|
87 |
* @attribute tickXArray |
|
|
88 |
* @description An array of page coordinates to use as X ticks for drag movement. |
|
|
89 |
* @type Array |
|
|
90 |
*/ |
|
|
91 |
tickXArray: { |
|
|
92 |
value: false |
|
|
93 |
}, |
|
|
94 |
/** |
|
|
95 |
* @attribute tickYArray |
|
|
96 |
* @description An array of page coordinates to use as Y ticks for drag movement. |
|
|
97 |
* @type Array |
|
|
98 |
*/ |
|
|
99 |
tickYArray: { |
|
|
100 |
value: false |
|
|
101 |
}, |
|
|
102 |
/** |
|
|
103 |
* @attribute constrain2region |
|
|
104 |
* @description An Object Literal containing a valid region (top, right, bottom, left) of page positions to constrain the drag node to. |
|
|
105 |
* @type Object |
|
|
106 |
*/ |
|
|
107 |
constrain2region: { |
|
|
108 |
value: false, |
|
|
109 |
getter: function(r) { |
|
|
110 |
if (Y.Lang.isObject(r)) { |
|
|
111 |
var o = {}; |
|
|
112 |
Y.mix(o, r); |
|
|
113 |
return o; |
|
|
114 |
} else { |
|
|
115 |
return false; |
|
|
116 |
} |
|
|
117 |
}, |
|
|
118 |
setter: function (r) { |
|
|
119 |
if (Y.Lang.isObject(r)) { |
|
|
120 |
if (Y.Lang.isNumber(r[TOP]) && Y.Lang.isNumber(r[RIGHT]) && Y.Lang.isNumber(r[LEFT]) && Y.Lang.isNumber(r[BOTTOM])) { |
|
|
121 |
var o = {}; |
|
|
122 |
Y.mix(o, r); |
|
|
123 |
return o; |
|
|
124 |
} else { |
|
|
125 |
return false; |
|
|
126 |
} |
|
|
127 |
} else if (r !== false) { |
|
|
128 |
return false; |
|
|
129 |
} |
|
|
130 |
return r; |
|
|
131 |
} |
|
|
132 |
}, |
|
|
133 |
/** |
|
|
134 |
* @attribute gutter |
|
|
135 |
* @description CSS style string for the gutter of a region (supports negative values): '5 0' (sets top and bottom to 5px, left and right to 0px), '1 2 3 4' (top 1px, right 2px, bottom 3px, left 4px) |
|
|
136 |
* @type String |
|
|
137 |
*/ |
|
|
138 |
gutter: { |
|
|
139 |
value: '0', |
|
|
140 |
setter: function(gutter) { |
|
|
141 |
return Y.DD.DDM.cssSizestoObject(gutter); |
|
|
142 |
} |
|
|
143 |
}, |
|
|
144 |
/** |
|
|
145 |
* @attribute constrain2node |
|
|
146 |
* @description Will attempt to constrain the drag node to the bounderies of this node. |
|
|
147 |
* @type Object |
|
|
148 |
*/ |
|
|
149 |
constrain2node: { |
|
|
150 |
value: false, |
|
|
151 |
setter: function(n) { |
|
|
152 |
if (!this.get(CON_2_REGION)) { |
|
|
153 |
var node = Y.Node.get(n); |
|
|
154 |
if (node) { |
|
|
155 |
return node; |
|
|
156 |
} |
|
|
157 |
} else if (this.get(CON_2_REGION) !== false) { |
|
|
158 |
} |
|
|
159 |
return false; |
|
|
160 |
} |
|
|
161 |
}, |
|
|
162 |
/** |
|
|
163 |
* @attribute constrain2view |
|
|
164 |
* @description Will attempt to constrain the drag node to the bounderies of the viewport region. |
|
|
165 |
* @type Object |
|
|
166 |
*/ |
|
|
167 |
constrain2view: { |
|
|
168 |
value: false |
|
|
169 |
} |
|
|
170 |
}; |
|
|
171 |
|
|
|
172 |
proto = { |
|
|
173 |
initializer: function() { |
|
|
174 |
this.get(HOST).on('drag:start', Y.bind(this._handleStart, this)); |
|
|
175 |
this.get(HOST).after('drag:align', Y.bind(this.align, this)); |
|
|
176 |
}, |
|
|
177 |
/** |
|
|
178 |
* @private |
|
|
179 |
* @method _handleStart |
|
|
180 |
* @description Fires on drag:start and clears the _regionCache |
|
|
181 |
*/ |
|
|
182 |
_handleStart: function() { |
|
|
183 |
this._regionCache = null; |
|
|
184 |
}, |
|
|
185 |
/** |
|
|
186 |
* @private |
|
|
187 |
* @property _regionCache |
|
|
188 |
* @description Store a cache of the region that we are constraining to |
|
|
189 |
* @type Object |
|
|
190 |
*/ |
|
|
191 |
_regionCache: null, |
|
|
192 |
/** |
|
|
193 |
* @private |
|
|
194 |
* @method _cacheRegion |
|
|
195 |
* @description Get's the region and caches it, called from window.resize and when the cache is null |
|
|
196 |
*/ |
|
|
197 |
_cacheRegion: function() { |
|
|
198 |
this._regionCache = this.get(CON_2_NODE).get('region'); |
|
|
199 |
}, |
|
|
200 |
/** |
|
|
201 |
* @method getRegion |
|
|
202 |
* @description Get the active region: viewport, node, custom region |
|
|
203 |
* @param {Boolean} inc Include the node's height and width |
|
|
204 |
* @return {Object} |
|
|
205 |
*/ |
|
|
206 |
getRegion: function(inc) { |
|
|
207 |
var r = {}, oh = null, ow = null, |
|
|
208 |
g = this.get('gutter'), |
|
|
209 |
host = this.get(HOST); |
|
|
210 |
|
|
|
211 |
if (this.get(CON_2_NODE)) { |
|
|
212 |
if (!this._regionCache) { |
|
|
213 |
Y.on('resize', Y.bind(this._cacheRegion, this), window); |
|
|
214 |
this._cacheRegion(); |
|
|
215 |
} |
|
|
216 |
r = Y.clone(this._regionCache); |
|
|
217 |
} else if (this.get(CON_2_REGION)) { |
|
|
218 |
r = this.get(CON_2_REGION); |
|
|
219 |
} else if (this.get('constrain2view')) { |
|
|
220 |
r = host.get(DRAG_NODE).get('viewportRegion'); |
|
|
221 |
} else { |
|
|
222 |
return false; |
|
|
223 |
} |
|
|
224 |
|
|
|
225 |
Y.each(g, function(i, n) { |
|
|
226 |
if ((n == RIGHT) || (n == BOTTOM)) { |
|
|
227 |
r[n] -= i; |
|
|
228 |
} else { |
|
|
229 |
r[n] += i; |
|
|
230 |
} |
|
|
231 |
}); |
|
|
232 |
if (inc) { |
|
|
233 |
oh = host.get(DRAG_NODE).get(OFFSET_HEIGHT); |
|
|
234 |
ow = host.get(DRAG_NODE).get(OFFSET_WIDTH); |
|
|
235 |
r[RIGHT] = r[RIGHT] - ow; |
|
|
236 |
r[BOTTOM] = r[BOTTOM] - oh; |
|
|
237 |
} |
|
|
238 |
return r; |
|
|
239 |
}, |
|
|
240 |
/** |
|
|
241 |
* @private |
|
|
242 |
* @method _checkRegion |
|
|
243 |
* @description Check if xy is inside a given region, if not change to it be inside. |
|
|
244 |
* @param {Array} _xy The XY to check if it's in the current region, if it isn't inside the region, it will reset the xy array to be inside the region. |
|
|
245 |
* @return {Array} The new XY that is inside the region |
|
|
246 |
*/ |
|
|
247 |
_checkRegion: function(_xy) { |
|
|
248 |
var oxy = _xy, |
|
|
249 |
r = this.getRegion(), |
|
|
250 |
host = this.get(HOST), |
|
|
251 |
oh = host.get(DRAG_NODE).get(OFFSET_HEIGHT), |
|
|
252 |
ow = host.get(DRAG_NODE).get(OFFSET_WIDTH); |
|
|
253 |
|
|
|
254 |
if (oxy[1] > (r[BOTTOM] - oh)) { |
|
|
255 |
_xy[1] = (r[BOTTOM] - oh); |
|
|
256 |
} |
|
|
257 |
if (r[TOP] > oxy[1]) { |
|
|
258 |
_xy[1] = r[TOP]; |
|
|
259 |
|
|
|
260 |
} |
|
|
261 |
if (oxy[0] > (r[RIGHT] - ow)) { |
|
|
262 |
_xy[0] = (r[RIGHT] - ow); |
|
|
263 |
} |
|
|
264 |
if (r[LEFT] > oxy[0]) { |
|
|
265 |
_xy[0] = r[LEFT]; |
|
|
266 |
} |
|
|
267 |
|
|
|
268 |
return _xy; |
|
|
269 |
}, |
|
|
270 |
/** |
|
|
271 |
* @method inRegion |
|
|
272 |
* @description Checks if the XY passed or the dragNode is inside the active region. |
|
|
273 |
* @param {Array} xy Optional XY to check, if not supplied this.get('dragNode').getXY() is used. |
|
|
274 |
* @return {Boolean} True if the XY is inside the region, false otherwise. |
|
|
275 |
*/ |
|
|
276 |
inRegion: function(xy) { |
|
|
277 |
xy = xy || this.get(HOST).get(DRAG_NODE).getXY(); |
|
|
278 |
|
|
|
279 |
var _xy = this._checkRegion([xy[0], xy[1]]), |
|
|
280 |
inside = false; |
|
|
281 |
if ((xy[0] === _xy[0]) && (xy[1] === _xy[1])) { |
|
|
282 |
inside = true; |
|
|
283 |
} |
|
|
284 |
return inside; |
|
|
285 |
}, |
|
|
286 |
/** |
|
|
287 |
* @method align |
|
|
288 |
* @description Modifies the Drag.actXY method from the after drag:align event. This is where the constraining happens. |
|
|
289 |
*/ |
|
|
290 |
align: function() { |
|
|
291 |
var host = this.get(HOST), |
|
|
292 |
_xy = host.actXY, |
|
|
293 |
r = this.getRegion(true); |
|
|
294 |
|
|
|
295 |
if (this.get('stickX')) { |
|
|
296 |
_xy[1] = (host.startXY[1] - host.deltaXY[1]); |
|
|
297 |
} |
|
|
298 |
if (this.get('stickY')) { |
|
|
299 |
_xy[0] = (host.startXY[0] - host.deltaXY[0]); |
|
|
300 |
} |
|
|
301 |
|
|
|
302 |
if (r) { |
|
|
303 |
_xy = this._checkRegion(_xy); |
|
|
304 |
} |
|
|
305 |
|
|
|
306 |
_xy = this._checkTicks(_xy, r); |
|
|
307 |
host.actXY = _xy; |
|
|
308 |
}, |
|
|
309 |
/** |
|
|
310 |
* @private |
|
|
311 |
* @method _checkTicks |
|
|
312 |
* @description This method delegates the proper helper method for tick calculations |
|
|
313 |
* @param {Array} xy The XY coords for the Drag |
|
|
314 |
* @param {Object} r The optional region that we are bound to. |
|
|
315 |
* @return {Array} The calced XY coords |
|
|
316 |
*/ |
|
|
317 |
_checkTicks: function(xy, r) { |
|
|
318 |
var host = this.get(HOST), |
|
|
319 |
lx = (host.startXY[0] - host.deltaXY[0]), |
|
|
320 |
ly = (host.startXY[1] - host.deltaXY[1]), |
|
|
321 |
xt = this.get('tickX'), |
|
|
322 |
yt = this.get('tickY'); |
|
|
323 |
if (xt && !this.get(TICK_X_ARRAY)) { |
|
|
324 |
xy[0] = DDM._calcTicks(xy[0], lx, xt, r[LEFT], r[RIGHT]); |
|
|
325 |
} |
|
|
326 |
if (yt && !this.get(TICK_Y_ARRAY)) { |
|
|
327 |
xy[1] = DDM._calcTicks(xy[1], ly, yt, r[TOP], r[BOTTOM]); |
|
|
328 |
} |
|
|
329 |
if (this.get(TICK_X_ARRAY)) { |
|
|
330 |
xy[0] = DDM._calcTickArray(xy[0], this.get(TICK_X_ARRAY), r[LEFT], r[RIGHT]); |
|
|
331 |
} |
|
|
332 |
if (this.get(TICK_Y_ARRAY)) { |
|
|
333 |
xy[1] = DDM._calcTickArray(xy[1], this.get(TICK_Y_ARRAY), r[TOP], r[BOTTOM]); |
|
|
334 |
} |
|
|
335 |
|
|
|
336 |
return xy; |
|
|
337 |
} |
|
|
338 |
}; |
|
|
339 |
|
|
|
340 |
Y.namespace('Plugin'); |
|
|
341 |
Y.extend(C, Y.Base, proto); |
|
|
342 |
Y.Plugin.DDConstrained = C; |
|
|
343 |
|
|
|
344 |
Y.mix(DDM, { |
|
|
345 |
/** |
|
|
346 |
* @for DDM |
|
|
347 |
* @namespace DD |
|
|
348 |
* @private |
|
|
349 |
* @method _calcTicks |
|
|
350 |
* @description Helper method to calculate the tick offsets for a given position |
|
|
351 |
* @param {Number} pos The current X or Y position |
|
|
352 |
* @param {Number} start The start X or Y position |
|
|
353 |
* @param {Number} tick The X or Y tick increment |
|
|
354 |
* @param {Number} off1 The min offset that we can't pass (region) |
|
|
355 |
* @param {Number} off2 The max offset that we can't pass (region) |
|
|
356 |
* @return {Number} The new position based on the tick calculation |
|
|
357 |
*/ |
|
|
358 |
_calcTicks: function(pos, start, tick, off1, off2) { |
|
|
359 |
var ix = ((pos - start) / tick), |
|
|
360 |
min = Math.floor(ix), |
|
|
361 |
max = Math.ceil(ix); |
|
|
362 |
if ((min !== 0) || (max !== 0)) { |
|
|
363 |
if ((ix >= min) && (ix <= max)) { |
|
|
364 |
pos = (start + (tick * min)); |
|
|
365 |
if (off1 && off2) { |
|
|
366 |
if (pos < off1) { |
|
|
367 |
pos = (start + (tick * (min + 1))); |
|
|
368 |
} |
|
|
369 |
if (pos > off2) { |
|
|
370 |
pos = (start + (tick * (min - 1))); |
|
|
371 |
} |
|
|
372 |
} |
|
|
373 |
} |
|
|
374 |
} |
|
|
375 |
return pos; |
|
|
376 |
}, |
|
|
377 |
/** |
|
|
378 |
* @for DDM |
|
|
379 |
* @namespace DD |
|
|
380 |
* @private |
|
|
381 |
* @method _calcTickArray |
|
|
382 |
* @description This method is used with the tickXArray and tickYArray config options |
|
|
383 |
* @param {Number} pos The current X or Y position |
|
|
384 |
* @param {Number} ticks The array containing our custom tick positions. |
|
|
385 |
* @param {Number} off1 The min offset that we can't pass (region) |
|
|
386 |
* @param {Number} off2 The max offset that we can't pass (region) |
|
|
387 |
* @return The tick position |
|
|
388 |
*/ |
|
|
389 |
_calcTickArray: function(pos, ticks, off1, off2) { |
|
|
390 |
var i = 0, len = ticks.length, next = 0, |
|
|
391 |
diff1, diff2, ret; |
|
|
392 |
|
|
|
393 |
if (!ticks || (ticks.length === 0)) { |
|
|
394 |
return pos; |
|
|
395 |
} else if (ticks[0] >= pos) { |
|
|
396 |
return ticks[0]; |
|
|
397 |
} else { |
|
|
398 |
for (i = 0; i < len; i++) { |
|
|
399 |
next = (i + 1); |
|
|
400 |
if (ticks[next] && ticks[next] >= pos) { |
|
|
401 |
diff1 = pos - ticks[i]; |
|
|
402 |
diff2 = ticks[next] - pos; |
|
|
403 |
ret = (diff2 > diff1) ? ticks[i] : ticks[next]; |
|
|
404 |
if (off1 && off2) { |
|
|
405 |
if (ret > off2) { |
|
|
406 |
if (ticks[i]) { |
|
|
407 |
ret = ticks[i]; |
|
|
408 |
} else { |
|
|
409 |
ret = ticks[len - 1]; |
|
|
410 |
} |
|
|
411 |
} |
|
|
412 |
} |
|
|
413 |
return ret; |
|
|
414 |
} |
|
|
415 |
|
|
|
416 |
} |
|
|
417 |
return ticks[ticks.length - 1]; |
|
|
418 |
} |
|
|
419 |
} |
|
|
420 |
}); |
|
|
421 |
|
|
|
422 |
|
|
|
423 |
|
|
|
424 |
|
|
|
425 |
}, '3.0.0b1' ,{requires:['dd-drag'], skinnable:false}); |