Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / form / FieldAncestor.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.FieldAncestor
17
18 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
19 items subtree. Adds the following capabilities:
20
21 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
22   instances at any depth within the container.
23 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
24   of individual fields at the container level.
25 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
26   container, to facilitate uniform configuration of all fields.
27
28 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
29 and should not normally need to be used directly.
30
31  * @markdown
32  * @docauthor Jason Johnston <jason@sencha.com>
33  */
34 Ext.define('Ext.form.FieldAncestor', {
35
36     /**
37      * @cfg {Object} fieldDefaults
38      * <p>If specified, the properties in this object are used as default config values for each
39      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
40      * that is added as a descendant of this container. Corresponding values specified in an individual field's
41      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
42      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
43      * options may be specified in the <tt>fieldDefaults</tt>.</p>
44      * <p>Example:</p>
45      * <pre><code>new Ext.form.Panel({
46     fieldDefaults: {
47         labelAlign: 'left',
48         labelWidth: 100
49     },
50     items: [{
51         xtype: 'fieldset',
52         defaults: {
53             labelAlign: 'top'
54         },
55         items: [{
56             name: 'field1'
57         }, {
58             name: 'field2'
59         }]
60     }, {
61         xtype: 'fieldset',
62         items: [{
63             name: 'field3',
64             labelWidth: 150
65         }, {
66             name: 'field4'
67         }]
68     }]
69 });</code></pre>
70      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
71      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
72      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
73      */
74
75
76     /**
77      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
78      * of any components importing this mixin.
79      */
80     initFieldAncestor: function() {
81         var me = this,
82             onSubtreeChange = me.onFieldAncestorSubtreeChange;
83
84         me.addEvents(
85             /**
86              * @event fieldvaliditychange
87              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
88              * container changes.
89              * @param {Ext.form.FieldAncestor} this
90              * @param {Ext.form.Labelable} The Field instance whose validity changed
91              * @param {String} isValid The field's new validity state
92              */
93             'fieldvaliditychange',
94
95             /**
96              * @event fielderrorchange
97              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
98              * instances within this container.
99              * @param {Ext.form.FieldAncestor} this
100              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
101              * @param {String} error The active error message
102              */
103             'fielderrorchange'
104         );
105
106         // Catch addition and removal of descendant fields
107         me.on('add', onSubtreeChange, me);
108         me.on('remove', onSubtreeChange, me);
109
110         me.initFieldDefaults();
111     },
112
113     /**
114      * @private Initialize the {@link #fieldDefaults} object
115      */
116     initFieldDefaults: function() {
117         if (!this.fieldDefaults) {
118             this.fieldDefaults = {};
119         }
120     },
121
122     /**
123      * @private
124      * Handle the addition and removal of components in the FieldAncestor component's child tree.
125      */
126     onFieldAncestorSubtreeChange: function(parent, child) {
127         var me = this,
128             isAdding = !!child.ownerCt;
129
130         function handleCmp(cmp) {
131             var isLabelable = cmp.isFieldLabelable,
132                 isField = cmp.isFormField;
133             if (isLabelable || isField) {
134                 if (isLabelable) {
135                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
136                 }
137                 if (isField) {
138                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
139                 }
140             }
141             else if (cmp.isContainer) {
142                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
143             }
144         }
145         handleCmp(child);
146     },
147
148     /**
149      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
150      * @param {Ext.form.Labelable} labelable The instance that was added
151      */
152     onLabelableAdded: function(labelable) {
153         var me = this;
154
155         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
156         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
157
158         labelable.setFieldDefaults(me.fieldDefaults);
159     },
160
161     /**
162      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
163      * @param {Ext.form.field.Field} field The field which was added
164      */
165     onFieldAdded: function(field) {
166         var me = this;
167         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
168     },
169
170     /**
171      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
172      * @param {Ext.form.Labelable} labelable The instance that was removed
173      */
174     onLabelableRemoved: function(labelable) {
175         var me = this;
176         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
177     },
178
179     /**
180      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
181      * @param {Ext.form.field.Field} field The field which was removed
182      */
183     onFieldRemoved: function(field) {
184         var me = this;
185         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
186     },
187
188     /**
189      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
190      */
191     handleFieldValidityChange: function(field, isValid) {
192         var me = this;
193         me.fireEvent('fieldvaliditychange', me, field, isValid);
194         me.onFieldValidityChange();
195     },
196
197     /**
198      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
199      */
200     handleFieldErrorChange: function(labelable, activeError) {
201         var me = this;
202         me.fireEvent('fielderrorchange', me, labelable, activeError);
203         me.onFieldErrorChange();
204     },
205
206     /**
207      * @protected Fired when the validity of any field within the container changes.
208      * @param {Ext.form.field.Field} The sub-field whose validity changed
209      * @param {String} The new validity state
210      */
211     onFieldValidityChange: Ext.emptyFn,
212
213     /**
214      * @protected Fired when the error message of any field within the container changes.
215      * @param {Ext.form.Labelable} The sub-field whose active error changed
216      * @param {String} The new active error message
217      */
218     onFieldErrorChange: Ext.emptyFn
219
220 });