Upgrade to ExtJS 4.0.2 - Released 06/09/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.core.Element
17  */
18 (function(){
19
20 var ELEMENT = Ext.core.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.core.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 {Array} 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 {Mixed} element The element to get the offsets from.
59       * @return {Array} 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.core.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.core.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.core.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.core.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.core.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.core.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 {Array} 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.core.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.core.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.core.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.core.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.core.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/Array} 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.core.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      * @return {Ext.core.Element} this
410      */
411     move: function(direction, distance, animate) {
412         var me = this,
413             xy = me.getXY(),
414             x = xy[0],
415             y = xy[1],
416             left = [x - distance, y],
417             right = [x + distance, y],
418             top = [x, y - distance],
419             bottom = [x, y + distance],
420             hash = {
421                 l: left,
422                 left: left,
423                 r: right,
424                 right: right,
425                 t: top,
426                 top: top,
427                 up: top,
428                 b: bottom,
429                 bottom: bottom,
430                 down: bottom
431             };
432
433         direction = direction.toLowerCase();
434         me.moveTo(hash[direction][0], hash[direction][1], animate);
435     },
436
437     /**
438      * Quick set left and top adding default units
439      * @param {String} left The left CSS property value
440      * @param {String} top The top CSS property value
441      * @return {Ext.core.Element} this
442      */
443     setLeftTop: function(left, top) {
444         var me = this,
445             style = me.dom.style;
446         style.left = me.addUnits(left);
447         style.top = me.addUnits(top);
448         return me;
449     },
450
451     /**
452      * Returns the region of this element.
453      * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
454      * @return {Region} A Ext.util.Region containing "top, left, bottom, right" member data.
455      */
456     getRegion: function() {
457         return this.getPageBox(true);
458     },
459
460     /**
461      * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
462      * @return {Region} A Ext.util.Region containing "top, left, bottom, right" member data.
463      */
464     getViewRegion: function() {
465         var me = this,
466             isBody = me.dom === document.body,
467             scroll, pos, top, left, width, height;
468             
469         // For the body we want to do some special logic
470         if (isBody) {
471             scroll = me.getScroll();
472             left = scroll.left;
473             top = scroll.top;
474             width = Ext.core.Element.getViewportWidth();
475             height = Ext.core.Element.getViewportHeight();
476         }
477         else {
478             pos = me.getXY();
479             left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
480             top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
481             width = me.getWidth(true);
482             height = me.getHeight(true);
483         }
484
485         return Ext.create('Ext.util.Region', top, left + width, top + height, left);
486     },
487
488     /**
489      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
490      * set another Element's size/location to match this element.
491      * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
492      * @return {Object} box An object in the format<pre><code>
493 {
494     x: &lt;Element's X position>,
495     y: &lt;Element's Y position>,
496     width: &lt;Element's width>,
497     height: &lt;Element's height>,
498     bottom: &lt;Element's lower bound>,
499     right: &lt;Element's rightmost bound>
500 }
501 </code></pre>
502      * The returned object may also be addressed as an Array where index 0 contains the X position
503      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
504      */
505     getPageBox : function(getRegion) {
506         var me = this,
507             el = me.dom,
508             isDoc = el === document.body,
509             w = isDoc ? Ext.core.Element.getViewWidth()  : el.offsetWidth,
510             h = isDoc ? Ext.core.Element.getViewHeight() : el.offsetHeight,
511             xy = me.getXY(),
512             t = xy[1],
513             r = xy[0] + w,
514             b = xy[1] + h,
515             l = xy[0];
516
517         if (getRegion) {
518             return Ext.create('Ext.util.Region', t, r, b, l);
519         }
520         else {
521             return {
522                 left: l,
523                 top: t,
524                 width: w,
525                 height: h,
526                 right: r,
527                 bottom: b
528             };
529         }
530     },
531
532     /**
533      * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
534      * @param {Number} x X value for new position (coordinates are page-based)
535      * @param {Number} y Y value for new position (coordinates are page-based)
536      * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
537      * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
538      * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
539      * </ul></div>
540      * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
541      * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
542      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
543      * </ul></div>
544      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
545      * @return {Ext.core.Element} this
546      */
547     setBounds: function(x, y, width, height, animate) {
548         var me = this;
549         if (!animate || !me.anim) {
550             me.setSize(width, height);
551             me.setLocation(x, y);
552         } else {
553             if (!Ext.isObject(animate)) {
554                 animate = {};
555             }
556             me.animate(Ext.applyIf({
557                 to: {
558                     x: x,
559                     y: y,
560                     width: me.adjustWidth(width),
561                     height: me.adjustHeight(height)
562                 }
563             }, animate));
564         }
565         return me;
566     },
567
568     /**
569      * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
570      * @param {Ext.util.Region} region The region to fill
571      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
572      * @return {Ext.core.Element} this
573      */
574     setRegion: function(region, animate) {
575         return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
576     }
577 });
578 })();
579