Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / form / FieldSet.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.form.FieldSet
17  * @extends Ext.container.Container
18  * 
19  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
20  * config will be rendered as the fieldset's `legend`.
21  * 
22  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
23  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
24  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
25  * layout type.
26  * 
27  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
28  * 
29  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
30  *    the {@link #title legend title}, or:
31  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
32  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
33  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
34  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
35  *
36  * {@img Ext.form.FieldSet/Ext.form.FieldSet.png Ext.form.FieldSet component}
37  *
38  * ## Example usage
39  * 
40  *     Ext.create('Ext.form.Panel', {
41  *         title: 'Simple Form with FieldSets',
42  *         labelWidth: 75, // label settings here cascade unless overridden
43  *         url: 'save-form.php',
44  *         frame: true,
45  *         bodyStyle: 'padding:5px 5px 0',
46  *         width: 550,
47  *         renderTo: Ext.getBody(),
48  *         layout: 'column', // arrange fieldsets side by side
49  *         defaults: {
50  *             bodyPadding: 4
51  *         },
52  *         items: [{
53  *             // Fieldset in Column 1 - collapsible via toggle button
54  *             xtype:'fieldset',
55  *             columnWidth: 0.5,
56  *             title: 'Fieldset 1',
57  *             collapsible: true,
58  *             defaultType: 'textfield',
59  *             defaults: {anchor: '100%'},
60  *             layout: 'anchor',
61  *             items :[{
62  *                 fieldLabel: 'Field 1',
63  *                 name: 'field1'
64  *             }, {
65  *                 fieldLabel: 'Field 2',
66  *                 name: 'field2'
67  *             }]
68  *         }, {
69  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
70  *             xtype:'fieldset',
71  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
72  *             columnWidth: 0.5,
73  *             checkboxToggle: true,
74  *             collapsed: true, // fieldset initially collapsed
75  *             layout:'anchor',
76  *             items :[{
77  *                 xtype: 'panel',
78  *                 anchor: '100%',
79  *                 title: 'Panel inside a fieldset',
80  *                 frame: true,
81  *                 height: 52
82  *             }]
83  *         }]
84  *     });
85  * 
86  * @docauthor Jason Johnston <jason@sencha.com>
87  */
88 Ext.define('Ext.form.FieldSet', {
89     extend: 'Ext.container.Container',
90     alias: 'widget.fieldset',
91     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
92
93     /**
94      * @cfg {String} title
95      * A title to be displayed in the fieldset's legend. May contain HTML markup.
96      */
97
98     /**
99      * @cfg {Boolean} checkboxToggle
100      * Set to <tt>true</tt> to render a checkbox into the fieldset frame just
101      * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults
102      * to <tt>false</tt>). This checkbox will be included in form submits using the {@link #checkboxName}.
103      */
104
105     /**
106      * @cfg {String} checkboxName
107      * The name to assign to the fieldset's checkbox if <tt>{@link #checkboxToggle} = true</tt>
108      * (defaults to <tt>'[fieldset id]-checkbox'</tt>).
109      */
110
111     /**
112      * @cfg {Boolean} collapsible
113      * Set to <tt>true</tt> to make the fieldset collapsible and have the expand/collapse toggle button automatically
114      * rendered into the legend element, <tt>false</tt> to keep the fieldset statically sized with no collapse
115      * button (defaults to <tt>false</tt>). Another option is to configure <tt>{@link #checkboxToggle}</tt>.
116      * Use the {@link #collapsed} config to collapse the fieldset by default.
117      */
118
119     /**
120      * @cfg {Boolean} collapsed
121      * Set to <tt>true</tt> to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified,
122      * the checkbox will also be unchecked by default.
123      */
124     collapsed: false,
125
126     /**
127      * @property legend
128      * @type Ext.Component
129      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend
130      * to be created, by setting the {@link #title} or {@link #checkboxToggle} options.
131      */
132
133     /**
134      * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to <tt>'x-fieldset'</tt>).
135      */
136     baseCls: Ext.baseCSSPrefix + 'fieldset',
137
138     /**
139      * @cfg {String} layout The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
140      * Defaults to <tt>'anchor'</tt>.
141      */
142     layout: 'anchor',
143
144     componentLayout: 'fieldset',
145
146     // No aria role necessary as fieldset has its own recognized semantics
147     ariaRole: '',
148
149     renderTpl: ['<div class="{baseCls}-body"></div>'],
150     
151     maskOnDisable: false,
152
153     getElConfig: function(){
154         return {tag: 'fieldset', id: this.id};
155     },
156
157     initComponent: function() {
158         var me = this,
159             baseCls = me.baseCls;
160
161         me.callParent();
162
163         // Create the Legend component if needed
164         me.initLegend();
165
166         // Add body el selector
167         Ext.applyIf(me.renderSelectors, {
168             body: '.' + baseCls + '-body'
169         });
170
171         if (me.collapsed) {
172             me.addCls(baseCls + '-collapsed');
173             me.collapse();
174         }
175     },
176
177     // private
178     onRender: function(container, position) {
179         this.callParent(arguments);
180         // Make sure the legend is created and rendered
181         this.initLegend();
182     },
183
184     /**
185      * @private
186      * Initialize and render the legend component if necessary
187      */
188     initLegend: function() {
189         var me = this,
190             legendItems,
191             legend = me.legend;
192
193         // Create the legend component if needed and it hasn't been already
194         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
195             legendItems = [];
196
197             // Checkbox
198             if (me.checkboxToggle) {
199                 legendItems.push(me.createCheckboxCmp());
200             }
201             // Toggle button
202             else if (me.collapsible) {
203                 legendItems.push(me.createToggleCmp());
204             }
205
206             // Title
207             legendItems.push(me.createTitleCmp());
208
209             legend = me.legend = Ext.create('Ext.container.Container', {
210                 baseCls: me.baseCls + '-header',
211                 ariaRole: '',
212                 getElConfig: function(){
213                     return {tag: 'legend', cls: this.baseCls};
214                 },
215                 items: legendItems
216             });
217         }
218
219         // Make sure legend is rendered if the fieldset is rendered
220         if (legend && !legend.rendered && me.rendered) {
221             me.legend.render(me.el, me.body); //insert before body element
222         }
223     },
224
225     /**
226      * @protected
227      * Creates the legend title component. This is only called internally, but could be overridden in subclasses
228      * to customize the title component.
229      * @return Ext.Component
230      */
231     createTitleCmp: function() {
232         var me = this;
233         me.titleCmp = Ext.create('Ext.Component', {
234             html: me.title,
235             cls: me.baseCls + '-header-text'
236         });
237         return me.titleCmp;
238         
239     },
240
241     /**
242      * @property checkboxCmp
243      * @type Ext.form.field.Checkbox
244      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
245      * populated if the fieldset is configured with <tt>{@link #checkboxToggle}:true</tt>.
246      */
247
248     /**
249      * @protected
250      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses
251      * to customize the checkbox's configuration or even return an entirely different component type.
252      * @return Ext.Component
253      */
254     createCheckboxCmp: function() {
255         var me = this,
256             suffix = '-checkbox';
257             
258         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
259             name: me.checkboxName || me.id + suffix,
260             cls: me.baseCls + '-header' + suffix,
261             checked: !me.collapsed,
262             listeners: {
263                 change: me.onCheckChange,
264                 scope: me
265             }
266         });
267         return me.checkboxCmp;
268     },
269
270     /**
271      * @property toggleCmp
272      * @type Ext.panel.Tool
273      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next
274      * to the title in the legend. Only populated if the fieldset is configured with <tt>{@link #collapsible}:true</tt>.
275      */
276
277     /**
278      * @protected
279      * Creates the toggle button component. This is only called internally, but could be overridden in
280      * subclasses to customize the toggle component.
281      * @return Ext.Component
282      */
283     createToggleCmp: function() {
284         var me = this;
285         me.toggleCmp = Ext.create('Ext.panel.Tool', {
286             type: 'toggle',
287             handler: me.toggle,
288             scope: me
289         });
290         return me.toggleCmp;
291     },
292     
293     /**
294      * Sets the title of this fieldset
295      * @param {String} title The new title
296      * @return {Ext.form.FieldSet} this
297      */
298     setTitle: function(title) {
299         var me = this;
300         me.title = title;
301         me.initLegend();
302         me.titleCmp.update(title);
303         return me;
304     },
305     
306     getTargetEl : function() {
307         return this.body || this.frameBody || this.el;
308     },
309     
310     getContentTarget: function() {
311         return this.body;
312     },
313     
314     /**
315      * @private
316      * Include the legend component in the items for ComponentQuery
317      */
318     getRefItems: function(deep) {
319         var refItems = this.callParent(arguments),
320             legend = this.legend;
321
322         // Prepend legend items to ensure correct order
323         if (legend) {
324             refItems.unshift(legend);
325             if (deep) {
326                 refItems.unshift.apply(refItems, legend.getRefItems(true));
327             }
328         }
329         return refItems;
330     },
331
332     /**
333      * Expands the fieldset.
334      * @return {Ext.form.FieldSet} this
335      */
336     expand : function(){
337         return this.setExpanded(true);
338     },
339     
340     /**
341      * Collapses the fieldset.
342      * @return {Ext.form.FieldSet} this
343      */
344     collapse : function() {
345         return this.setExpanded(false);
346     },
347
348     /**
349      * @private Collapse or expand the fieldset
350      */
351     setExpanded: function(expanded) {
352         var me = this,
353             checkboxCmp = me.checkboxCmp;
354
355         expanded = !!expanded;
356         
357         if (checkboxCmp) {
358             checkboxCmp.setValue(expanded);
359         }
360         
361         if (expanded) {
362             me.removeCls(me.baseCls + '-collapsed');
363         } else {
364             me.addCls(me.baseCls + '-collapsed');
365         }
366         me.collapsed = !expanded;
367         if (expanded) {
368             // ensure subitems will get rendered and layed out when expanding
369             me.getComponentLayout().childrenChanged = true;
370         }
371         me.doComponentLayout();
372         return me;
373     },
374
375     /**
376      * Toggle the fieldset's collapsed state to the opposite of what it is currently
377      */
378     toggle: function() {
379         this.setExpanded(!!this.collapsed);
380     },
381
382     /**
383      * @private Handle changes in the checkbox checked state
384      */
385     onCheckChange: function(cmp, checked) {
386         this.setExpanded(checked);
387     },
388
389     beforeDestroy : function() {
390         var legend = this.legend;
391         if (legend) {
392             legend.destroy();
393         }
394         this.callParent();
395     }
396 });
397