Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / layout / container / Anchor.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.layout.container.Anchor
17  * @extends Ext.layout.container.Container
18  * 
19  * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
20  * If the container is resized, all anchored items are automatically rerendered according to their
21  * <b><tt>{@link #anchor}</tt></b> rules.
22  *
23  * This class is intended to be extended or created via the layout: 'anchor' {@link Ext.layout.container.AbstractContainer#layout}
24  * config, and should generally not need to be created directly via the new keyword.</p>
25  * 
26  * AnchorLayout does not have any direct config options (other than inherited ones). By default,
27  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
28  * container using the AnchorLayout can supply an anchoring-specific config property of <b>anchorSize</b>.
29  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
30  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
31  * logic if necessary.  
32  *
33  * {@img Ext.layout.container.Anchor/Ext.layout.container.Anchor.png Ext.layout.container.Anchor container layout}
34  *
35  * For example:
36  *     Ext.create('Ext.Panel', {
37  *         width: 500,
38  *         height: 400,
39  *         title: "AnchorLayout Panel",
40  *         layout: 'anchor',
41  *         renderTo: Ext.getBody(),
42  *         items: [{
43  *             xtype: 'panel',
44  *             title: '75% Width and 20% Height',
45  *             anchor: '75% 20%'
46  *         },{
47  *             xtype: 'panel',
48  *             title: 'Offset -300 Width & -200 Height',
49  *             anchor: '-300 -200'              
50  *         },{
51  *             xtype: 'panel',
52  *             title: 'Mixed Offset and Percent',
53  *             anchor: '-250 20%'
54  *         }]
55  *     });
56  */
57
58 Ext.define('Ext.layout.container.Anchor', {
59
60     /* Begin Definitions */
61
62     alias: 'layout.anchor',
63     extend: 'Ext.layout.container.Container',
64     alternateClassName: 'Ext.layout.AnchorLayout',
65
66     /* End Definitions */
67
68     /**
69      * @cfg {String} anchor
70      * <p>This configuation option is to be applied to <b>child <tt>items</tt></b> of a container managed by
71      * this layout (ie. configured with <tt>layout:'anchor'</tt>).</p><br/>
72      *
73      * <p>This value is what tells the layout how an item should be anchored to the container. <tt>items</tt>
74      * added to an AnchorLayout accept an anchoring-specific config property of <b>anchor</b> which is a string
75      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
76      * The following types of anchor values are supported:<div class="mdetail-params"><ul>
77      *
78      * <li><b>Percentage</b> : Any value between 1 and 100, expressed as a percentage.<div class="sub-desc">
79      * The first anchor is the percentage width that the item should take up within the container, and the
80      * second is the percentage height.  For example:<pre><code>
81 // two values specified
82 anchor: '100% 50%' // render item complete width of the container and
83                    // 1/2 height of the container
84 // one value specified
85 anchor: '100%'     // the width value; the height will default to auto
86      * </code></pre></div></li>
87      *
88      * <li><b>Offsets</b> : Any positive or negative integer value.<div class="sub-desc">
89      * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
90      * and the second is the offset from the bottom edge. For example:<pre><code>
91 // two values specified
92 anchor: '-50 -100' // render item the complete width of the container
93                    // minus 50 pixels and
94                    // the complete height minus 100 pixels.
95 // one value specified
96 anchor: '-50'      // anchor value is assumed to be the right offset value
97                    // bottom offset will default to 0
98      * </code></pre></div></li>
99      *
100      * <li><b>Sides</b> : Valid values are <tt>'right'</tt> (or <tt>'r'</tt>) and <tt>'bottom'</tt>
101      * (or <tt>'b'</tt>).<div class="sub-desc">
102      * Either the container must have a fixed size or an anchorSize config value defined at render time in
103      * order for these to have any effect.</div></li>
104      *
105      * <li><b>Mixed</b> : <div class="sub-desc">
106      * Anchor values can also be mixed as needed.  For example, to render the width offset from the container
107      * right edge by 50 pixels and 75% of the container's height use:
108      * <pre><code>
109 anchor: '-50 75%'
110      * </code></pre></div></li>
111      *
112      *
113      * </ul></div>
114      */
115
116     type: 'anchor',
117
118     /**
119      * @cfg {String} defaultAnchor
120      * Default anchor for all child <b>container</b> items applied if no anchor or specific width is set on the child item.  Defaults to '100%'.
121      */
122     defaultAnchor: '100%',
123
124     parseAnchorRE: /^(r|right|b|bottom)$/i,
125
126     // private
127     onLayout: function() {
128         this.callParent(arguments);
129
130         var me = this,
131             size = me.getLayoutTargetSize(),
132             owner = me.owner,
133             target = me.getTarget(),
134             ownerWidth = size.width,
135             ownerHeight = size.height,
136             overflow = target.getStyle('overflow'),
137             components = me.getVisibleItems(owner),
138             len = components.length,
139             boxes = [],
140             box, newTargetSize, component, anchorSpec, calcWidth, calcHeight,
141             i, el, cleaner;
142
143         if (ownerWidth < 20 && ownerHeight < 20) {
144             return;
145         }
146
147         // Anchor layout uses natural HTML flow to arrange the child items.
148         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
149         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
150         if (!me.clearEl) {
151             me.clearEl = target.createChild({
152                 cls: Ext.baseCSSPrefix + 'clear',
153                 role: 'presentation'
154             });
155         }
156
157         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
158         if (!Ext.supports.RightMargin) {
159             cleaner = Ext.core.Element.getRightMarginFixCleaner(target);
160             target.addCls(Ext.baseCSSPrefix + 'inline-children');
161         }
162
163         for (i = 0; i < len; i++) {
164             component = components[i];
165             el = component.el;
166
167             anchorSpec = component.anchorSpec;
168             if (anchorSpec) {
169                 if (anchorSpec.right) {
170                     calcWidth = me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component);
171                 } else {
172                     calcWidth = undefined;
173                 }
174                 if (anchorSpec.bottom) {
175                     calcHeight = me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component);
176                 } else {
177                     calcHeight = undefined;
178                 }
179
180                 boxes.push({
181                     component: component,
182                     anchor: true,
183                     width: calcWidth || undefined,
184                     height: calcHeight || undefined
185                 });
186             } else {
187                 boxes.push({
188                     component: component,
189                     anchor: false
190                 });
191             }
192         }
193
194         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
195         if (!Ext.supports.RightMargin) {
196             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
197             cleaner();
198         }
199
200         for (i = 0; i < len; i++) {
201             box = boxes[i];
202             me.setItemSize(box.component, box.width, box.height);
203         }
204
205         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
206             newTargetSize = me.getLayoutTargetSize();
207             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
208                 me.adjustmentPass = true;
209                 me.onLayout();
210             }
211         }
212
213         delete me.adjustmentPass;
214     },
215
216     // private
217     parseAnchor: function(a, start, cstart) {
218         if (a && a != 'none') {
219             var ratio;
220             // standard anchor
221             if (this.parseAnchorRE.test(a)) {
222                 var diff = cstart - start;
223                 return function(v) {
224                     return v - diff;
225                 };
226             }    
227             // percentage
228             else if (a.indexOf('%') != -1) {
229                 ratio = parseFloat(a.replace('%', '')) * 0.01;
230                 return function(v) {
231                     return Math.floor(v * ratio);
232                 };
233             }    
234             // simple offset adjustment
235             else {
236                 a = parseInt(a, 10);
237                 if (!isNaN(a)) {
238                     return function(v) {
239                         return v + a;
240                     };
241                 }
242             }
243         }
244         return null;
245     },
246
247     // private
248     adjustWidthAnchor: function(value, comp) {
249         return value;
250     },
251
252     // private
253     adjustHeightAnchor: function(value, comp) {
254         return value;
255     },
256
257     configureItem: function(item) {
258         var me = this,
259             owner = me.owner,
260             anchor= item.anchor,
261             anchorsArray,
262             anchorSpec,
263             anchorWidth,
264             anchorHeight;
265
266         if (!item.anchor && item.items && !Ext.isNumber(item.width) && !(Ext.isIE6 && Ext.isStrict)) {
267             item.anchor = anchor = me.defaultAnchor;
268         }
269
270         // find the container anchoring size
271         if (owner.anchorSize) {
272             if (typeof owner.anchorSize == 'number') {
273                 anchorWidth = owner.anchorSize;
274             }
275             else {
276                 anchorWidth = owner.anchorSize.width;
277                 anchorHeight = owner.anchorSize.height;
278             }
279         }
280         else {
281             anchorWidth = owner.initialConfig.width;
282             anchorHeight = owner.initialConfig.height;
283         }
284
285         if (anchor) {
286             // cache all anchor values
287             anchorsArray = anchor.split(' ');
288             item.anchorSpec = anchorSpec = {
289                 right: me.parseAnchor(anchorsArray[0], item.initialConfig.width, anchorWidth),
290                 bottom: me.parseAnchor(anchorsArray[1], item.initialConfig.height, anchorHeight)
291             };
292
293             if (anchorSpec.right) {
294                 item.layoutManagedWidth = 1;
295             } else {
296                 item.layoutManagedWidth = 2;
297             }
298
299             if (anchorSpec.bottom) {
300                 item.layoutManagedHeight = 1;
301             } else {
302                 item.layoutManagedHeight = 2;
303             }
304         } else {
305             item.layoutManagedWidth = 2;
306             item.layoutManagedHeight = 2;
307         }
308         this.callParent(arguments);
309     }
310
311 });