]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/dd/dd-constrain.js
Release 6.2.0beta4
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / dd / dd-constrain.js
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.0
6 build: 1549
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 boundaries 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 boundaries 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.0' ,{requires:['dd-drag'], skinnable:false});