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