Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / dom / Element.position.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.Element
17  */
18 (function(){
19
20 var ELEMENT = Ext.Element,
21     LEFT = "left",
22     RIGHT = "right",
23     TOP = "top",
24     BOTTOM = "bottom",
25     POSITION = "position",
26     STATIC = "static",
27     RELATIVE = "relative",
28     AUTO = "auto",
29     ZINDEX = "z-index";
30
31 Ext.override(Ext.Element, {
32     /**
33       * Gets the current X position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
34       * @return {Number} The X position of the element
35       */
36     getX : function(){
37         return ELEMENT.getX(this.dom);
38     },
39
40     /**
41       * Gets the current Y position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
42       * @return {Number} The Y position of the element
43       */
44     getY : function(){
45         return ELEMENT.getY(this.dom);
46     },
47
48     /**
49       * Gets the current position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
50       * @return {Number[]} The XY position of the element
51       */
52     getXY : function(){
53         return ELEMENT.getXY(this.dom);
54     },
55
56     /**
57       * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree and not have display:none to have page coordinates.
58       * @param {String/HTMLElement/Ext.Element} element The element to get the offsets from.
59       * @return {Number[]} The XY page offsets (e.g. [100, -200])
60       */
61     getOffsetsTo : function(el){
62         var o = this.getXY(),
63             e = Ext.fly(el, '_internal').getXY();
64         return [o[0]-e[0],o[1]-e[1]];
65     },
66
67     /**
68      * Sets the X position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
69      * @param {Number} The X position of the element
70      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
71      * @return {Ext.Element} this
72      */
73     setX : function(x, animate){
74         return this.setXY([x, this.getY()], animate);
75     },
76
77     /**
78      * Sets the Y position of the element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
79      * @param {Number} The Y position of the element
80      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
81      * @return {Ext.Element} this
82      */
83     setY : function(y, animate){
84         return this.setXY([this.getX(), y], animate);
85     },
86
87     /**
88      * Sets the element's left position directly using CSS style (instead of {@link #setX}).
89      * @param {String} left The left CSS property value
90      * @return {Ext.Element} this
91      */
92     setLeft : function(left){
93         this.setStyle(LEFT, this.addUnits(left));
94         return this;
95     },
96
97     /**
98      * Sets the element's top position directly using CSS style (instead of {@link #setY}).
99      * @param {String} top The top CSS property value
100      * @return {Ext.Element} this
101      */
102     setTop : function(top){
103         this.setStyle(TOP, this.addUnits(top));
104         return this;
105     },
106
107     /**
108      * Sets the element's CSS right style.
109      * @param {String} right The right CSS property value
110      * @return {Ext.Element} this
111      */
112     setRight : function(right){
113         this.setStyle(RIGHT, this.addUnits(right));
114         return this;
115     },
116
117     /**
118      * Sets the element's CSS bottom style.
119      * @param {String} bottom The bottom CSS property value
120      * @return {Ext.Element} this
121      */
122     setBottom : function(bottom){
123         this.setStyle(BOTTOM, this.addUnits(bottom));
124         return this;
125     },
126
127     /**
128      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
129      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
130      * @param {Number[]} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
131      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
132      * @return {Ext.Element} this
133      */
134     setXY: function(pos, animate) {
135         var me = this;
136         if (!animate || !me.anim) {
137             ELEMENT.setXY(me.dom, pos);
138         }
139         else {
140             if (!Ext.isObject(animate)) {
141                 animate = {};
142             }
143             me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
144         }
145         return me;
146     },
147
148     /**
149      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
150      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
151      * @param {Number} x X value for new position (coordinates are page-based)
152      * @param {Number} y Y value for new position (coordinates are page-based)
153      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
154      * @return {Ext.Element} this
155      */
156     setLocation : function(x, y, animate){
157         return this.setXY([x, y], animate);
158     },
159
160     /**
161      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
162      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
163      * @param {Number} x X value for new position (coordinates are page-based)
164      * @param {Number} y Y value for new position (coordinates are page-based)
165      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
166      * @return {Ext.Element} this
167      */
168     moveTo : function(x, y, animate){
169         return this.setXY([x, y], animate);
170     },
171
172     /**
173      * Gets the left X coordinate
174      * @param {Boolean} local True to get the local css position instead of page coordinate
175      * @return {Number}
176      */
177     getLeft : function(local){
178         return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
179     },
180
181     /**
182      * Gets the right X coordinate of the element (element X position + element width)
183      * @param {Boolean} local True to get the local css position instead of page coordinate
184      * @return {Number}
185      */
186     getRight : function(local){
187         var me = this;
188         return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
189     },
190
191     /**
192      * Gets the top Y coordinate
193      * @param {Boolean} local True to get the local css position instead of page coordinate
194      * @return {Number}
195      */
196     getTop : function(local) {
197         return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
198     },
199
200     /**
201      * Gets the bottom Y coordinate of the element (element Y position + element height)
202      * @param {Boolean} local True to get the local css position instead of page coordinate
203      * @return {Number}
204      */
205     getBottom : function(local){
206         var me = this;
207         return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
208     },
209
210     /**
211     * Initializes positioning on this element. If a desired position is not passed, it will make the
212     * the element positioned relative IF it is not already positioned.
213     * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
214     * @param {Number} zIndex (optional) The zIndex to apply
215     * @param {Number} x (optional) Set the page X position
216     * @param {Number} y (optional) Set the page Y position
217     */
218     position : function(pos, zIndex, x, y) {
219         var me = this;
220
221         if (!pos && me.isStyle(POSITION, STATIC)){
222             me.setStyle(POSITION, RELATIVE);
223         } else if(pos) {
224             me.setStyle(POSITION, pos);
225         }
226         if (zIndex){
227             me.setStyle(ZINDEX, zIndex);
228         }
229         if (x || y) {
230             me.setXY([x || false, y || false]);
231         }
232     },
233
234     /**
235     * Clear positioning back to the default when the document was loaded
236     * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
237     * @return {Ext.Element} this
238      */
239     clearPositioning : function(value){
240         value = value || '';
241         this.setStyle({
242             left : value,
243             right : value,
244             top : value,
245             bottom : value,
246             "z-index" : "",
247             position : STATIC
248         });
249         return this;
250     },
251
252     /**
253     * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
254     * snapshot before performing an update and then restoring the element.
255     * @return {Object}
256     */
257     getPositioning : function(){
258         var l = this.getStyle(LEFT);
259         var t = this.getStyle(TOP);
260         return {
261             "position" : this.getStyle(POSITION),
262             "left" : l,
263             "right" : l ? "" : this.getStyle(RIGHT),
264             "top" : t,
265             "bottom" : t ? "" : this.getStyle(BOTTOM),
266             "z-index" : this.getStyle(ZINDEX)
267         };
268     },
269
270     /**
271     * Set positioning with an object returned by getPositioning().
272     * @param {Object} posCfg
273     * @return {Ext.Element} this
274      */
275     setPositioning : function(pc){
276         var me = this,
277             style = me.dom.style;
278
279         me.setStyle(pc);
280
281         if(pc.right == AUTO){
282             style.right = "";
283         }
284         if(pc.bottom == AUTO){
285             style.bottom = "";
286         }
287
288         return me;
289     },
290
291     /**
292      * Translates the passed page coordinates into left/top css values for this element
293      * @param {Number/Number[]} x The page x or an array containing [x, y]
294      * @param {Number} y (optional) The page y, required if x is not an array
295      * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
296      */
297     translatePoints: function(x, y) {
298         if (Ext.isArray(x)) {
299              y = x[1];
300              x = x[0];
301         }
302         var me = this,
303             relative = me.isStyle(POSITION, RELATIVE),
304             o = me.getXY(),
305             left = parseInt(me.getStyle(LEFT), 10),
306             top = parseInt(me.getStyle(TOP), 10);
307
308         if (!Ext.isNumber(left)) {
309             left = relative ? 0 : me.dom.offsetLeft;
310         }
311         if (!Ext.isNumber(top)) {
312             top = relative ? 0 : me.dom.offsetTop;
313         }
314         left = (Ext.isNumber(x)) ? x - o[0] + left : undefined;
315         top = (Ext.isNumber(y)) ? y - o[1] + top : undefined;
316         return {
317             left: left,
318             top: top
319         };
320     },
321
322     /**
323      * Sets the element's box. Use getBox() on another element to get a box obj. If animate is true then width, height, x and y will be animated concurrently.
324      * @param {Object} box The box to fill {x, y, width, height}
325      * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
326      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
327      * @return {Ext.Element} this
328      */
329     setBox: function(box, adjust, animate) {
330         var me = this,
331             w = box.width,
332             h = box.height;
333         if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
334             w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
335             h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
336         }
337         me.setBounds(box.x, box.y, w, h, animate);
338         return me;
339     },
340
341     /**
342      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
343      * set another Element's size/location to match this element.
344      * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
345      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
346      * @return {Object} box An object in the format<pre><code>
347 {
348     x: &lt;Element's X position>,
349     y: &lt;Element's Y position>,
350     width: &lt;Element's width>,
351     height: &lt;Element's height>,
352     bottom: &lt;Element's lower bound>,
353     right: &lt;Element's rightmost bound>
354 }
355 </code></pre>
356      * The returned object may also be addressed as an Array where index 0 contains the X position
357      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
358      */
359     getBox: function(contentBox, local) {
360         var me = this,
361             xy,
362             left,
363             top,
364             getBorderWidth = me.getBorderWidth,
365             getPadding = me.getPadding,
366             l, r, t, b, w, h, bx;
367         if (!local) {
368             xy = me.getXY();
369         } else {
370             left = parseInt(me.getStyle("left"), 10) || 0;
371             top = parseInt(me.getStyle("top"), 10) || 0;
372             xy = [left, top];
373         }
374         w = me.getWidth();
375         h = me.getHeight();
376         if (!contentBox) {
377             bx = {
378                 x: xy[0],
379                 y: xy[1],
380                 0: xy[0],
381                 1: xy[1],
382                 width: w,
383                 height: h
384             };
385         } else {
386             l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
387             r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
388             t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
389             b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
390             bx = {
391                 x: xy[0] + l,
392                 y: xy[1] + t,
393                 0: xy[0] + l,
394                 1: xy[1] + t,
395                 width: w - (l + r),
396                 height: h - (t + b)
397             };
398         }
399         bx.right = bx.x + bx.width;
400         bx.bottom = bx.y + bx.height;
401         return bx;
402     },
403
404     /**
405      * Move this element relative to its current position.
406      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
407      * @param {Number} distance How far to move the element in pixels
408      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
409      */
410     move: function(direction, distance, animate) {
411         var me = this,
412             xy = me.getXY(),
413             x = xy[0],
414             y = xy[1],
415             left = [x - distance, y],
416             right = [x + distance, y],
417             top = [x, y - distance],
418             bottom = [x, y + distance],
419             hash = {
420                 l: left,
421                 left: left,
422                 r: right,
423                 right: right,
424                 t: top,
425                 top: top,
426                 up: top,
427                 b: bottom,
428                 bottom: bottom,
429                 down: bottom
430             };
431
432         direction = direction.toLowerCase();
433         me.moveTo(hash[direction][0], hash[direction][1], animate);
434     },
435
436     /**
437      * Quick set left and top adding default units
438      * @param {String} left The left CSS property value
439      * @param {String} top The top CSS property value
440      * @return {Ext.Element} this
441      */
442     setLeftTop: function(left, top) {
443         var me = this,
444             style = me.dom.style;
445         style.left = me.addUnits(left);
446         style.top = me.addUnits(top);
447         return me;
448     },
449
450     /**
451      * Returns the region of this element.
452      * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
453      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
454      */
455     getRegion: function() {
456         return this.getPageBox(true);
457     },
458
459     /**
460      * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
461      * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
462      */
463     getViewRegion: function() {
464         var me = this,
465             isBody = me.dom === document.body,
466             scroll, pos, top, left, width, height;
467
468         // For the body we want to do some special logic
469         if (isBody) {
470             scroll = me.getScroll();
471             left = scroll.left;
472             top = scroll.top;
473             width = Ext.Element.getViewportWidth();
474             height = Ext.Element.getViewportHeight();
475         }
476         else {
477             pos = me.getXY();
478             left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
479             top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
480             width = me.getWidth(true);
481             height = me.getHeight(true);
482         }
483
484         return Ext.create('Ext.util.Region', top, left + width, top + height, left);
485     },
486
487     /**
488      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
489      * set another Element's size/location to match this element.
490      * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
491      * @return {Object} box An object in the format<pre><code>
492 {
493     x: &lt;Element's X position>,
494     y: &lt;Element's Y position>,
495     width: &lt;Element's width>,
496     height: &lt;Element's height>,
497     bottom: &lt;Element's lower bound>,
498     right: &lt;Element's rightmost bound>
499 }
500 </code></pre>
501      * The returned object may also be addressed as an Array where index 0 contains the X position
502      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
503      */
504     getPageBox : function(getRegion) {
505         var me = this,
506             el = me.dom,
507             isDoc = el === document.body,
508             w = isDoc ? Ext.Element.getViewWidth()  : el.offsetWidth,
509             h = isDoc ? Ext.Element.getViewHeight() : el.offsetHeight,
510             xy = me.getXY(),
511             t = xy[1],
512             r = xy[0] + w,
513             b = xy[1] + h,
514             l = xy[0];
515
516         if (getRegion) {
517             return Ext.create('Ext.util.Region', t, r, b, l);
518         }
519         else {
520             return {
521                 left: l,
522                 top: t,
523                 width: w,
524                 height: h,
525                 right: r,
526                 bottom: b
527             };
528         }
529     },
530
531     /**
532      * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
533      * @param {Number} x X value for new position (coordinates are page-based)
534      * @param {Number} y Y value for new position (coordinates are page-based)
535      * @param {Number/String} width The new width. This may be one of:<div class="mdetail-params"><ul>
536      * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
537      * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
538      * </ul></div>
539      * @param {Number/String} height The new height. This may be one of:<div class="mdetail-params"><ul>
540      * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
541      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
542      * </ul></div>
543      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
544      * @return {Ext.Element} this
545      */
546     setBounds: function(x, y, width, height, animate) {
547         var me = this;
548         if (!animate || !me.anim) {
549             me.setSize(width, height);
550             me.setLocation(x, y);
551         } else {
552             if (!Ext.isObject(animate)) {
553                 animate = {};
554             }
555             me.animate(Ext.applyIf({
556                 to: {
557                     x: x,
558                     y: y,
559                     width: me.adjustWidth(width),
560                     height: me.adjustHeight(height)
561                 }
562             }, animate));
563         }
564         return me;
565     },
566
567     /**
568      * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
569      * @param {Ext.util.Region} region The region to fill
570      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
571      * @return {Ext.Element} this
572      */
573     setRegion: function(region, animate) {
574         return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
575     }
576 });
577 })();
578