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