Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / docs / source / CompositeField.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.1
11  * Copyright(c) 2006-2010 Sencha Inc.
12  * licensing@sencha.com
13  * http://www.sencha.com/license
14  */
15 <div id="cls-Ext.form.CompositeField"></div>/**
16  * @class Ext.form.CompositeField
17  * @extends Ext.form.Field
18  * Composite field allowing a number of form Fields to be rendered on the same row. The fields are rendered
19  * using an hbox layout internally, so all of the normal HBox layout config items are available. Example usage:
20  * <pre>
21 {
22     xtype: 'compositefield',
23     labelWidth: 120
24     items: [
25         {
26             xtype     : 'textfield',
27             fieldLabel: 'Title',
28             width     : 20
29         },
30         {
31             xtype     : 'textfield',
32             fieldLabel: 'First',
33             flex      : 1
34         },
35         {
36             xtype     : 'textfield',
37             fieldLabel: 'Last',
38             flex      : 1
39         }
40     ]
41 }
42  * </pre>
43  * In the example above the composite's fieldLabel will be set to 'Title, First, Last' as it groups the fieldLabels
44  * of each of its children. This can be overridden by setting a fieldLabel on the compositefield itself:
45  * <pre>
46 {
47     xtype: 'compositefield',
48     fieldLabel: 'Custom label',
49     items: [...]
50 }
51  * </pre>
52  * Any Ext.form.* component can be placed inside a composite field.
53  */
54 Ext.form.CompositeField = Ext.extend(Ext.form.Field, {
55
56     <div id="prop-Ext.form.CompositeField-defaultMargins"></div>/**
57      * @property defaultMargins
58      * @type String
59      * The margins to apply by default to each field in the composite
60      */
61     defaultMargins: '0 5 0 0',
62
63     <div id="prop-Ext.form.CompositeField-skipLastItemMargin"></div>/**
64      * @property skipLastItemMargin
65      * @type Boolean
66      * If true, the defaultMargins are not applied to the last item in the composite field set (defaults to true)
67      */
68     skipLastItemMargin: true,
69
70     <div id="prop-Ext.form.CompositeField-isComposite"></div>/**
71      * @property isComposite
72      * @type Boolean
73      * Signifies that this is a Composite field
74      */
75     isComposite: true,
76
77     <div id="prop-Ext.form.CompositeField-combineErrors"></div>/**
78      * @property combineErrors
79      * @type Boolean
80      * True to combine errors from the individual fields into a single error message at the CompositeField level (defaults to true)
81      */
82     combineErrors: true,
83     
84     <div id="cfg-Ext.form.CompositeField-labelConnector"></div>/**
85      * @cfg {String} labelConnector The string to use when joining segments of the built label together (defaults to ', ')
86      */
87     labelConnector: ', ',
88     
89     <div id="cfg-Ext.form.CompositeField-defaults"></div>/**
90      * @cfg {Object} defaults Any default properties to assign to the child fields.
91      */
92
93     //inherit docs
94     //Builds the composite field label
95     initComponent: function() {
96         var labels = [],
97             items  = this.items,
98             item;
99
100         for (var i=0, j = items.length; i < j; i++) {
101             item = items[i];
102             
103             if (!Ext.isEmpty(item.ref)){
104                 item.ref = '../' + item.ref;
105             }
106
107             labels.push(item.fieldLabel);
108
109             //apply any defaults
110             Ext.applyIf(item, this.defaults);
111
112             //apply default margins to each item except the last
113             if (!(i == j - 1 && this.skipLastItemMargin)) {
114                 Ext.applyIf(item, {margins: this.defaultMargins});
115             }
116         }
117
118         this.fieldLabel = this.fieldLabel || this.buildLabel(labels);
119
120         <div id="prop-Ext.form.CompositeField-fieldErrors"></div>/**
121          * @property fieldErrors
122          * @type Ext.util.MixedCollection
123          * MixedCollection of current errors on the Composite's subfields. This is used internally to track when
124          * to show and hide error messages at the Composite level. Listeners are attached to the MixedCollection's
125          * add, remove and replace events to update the error icon in the UI as errors are added or removed.
126          */
127         this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
128             return item.field;
129         });
130
131         this.fieldErrors.on({
132             scope  : this,
133             add    : this.updateInvalidMark,
134             remove : this.updateInvalidMark,
135             replace: this.updateInvalidMark
136         });
137
138         Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);
139         
140         this.innerCt = new Ext.Container({
141             layout  : 'hbox',
142             items   : this.items,
143             cls     : 'x-form-composite',
144             defaultMargins: '0 3 0 0',
145             ownerCt: this
146         });
147         this.innerCt.ownerCt = undefined;
148         
149         var fields = this.innerCt.findBy(function(c) {
150             return c.isFormField;
151         }, this);
152
153         <div id="prop-Ext.form.CompositeField-items"></div>/**
154          * @property items
155          * @type Ext.util.MixedCollection
156          * Internal collection of all of the subfields in this Composite
157          */
158         this.items = new Ext.util.MixedCollection();
159         this.items.addAll(fields);
160         
161     },
162
163     /**
164      * @private
165      * Creates an internal container using hbox and renders the fields to it
166      */
167     onRender: function(ct, position) {
168         if (!this.el) {
169             <div id="prop-Ext.form.CompositeField-innerCt"></div>/**
170              * @property innerCt
171              * @type Ext.Container
172              * A container configured with hbox layout which is responsible for laying out the subfields
173              */
174             var innerCt = this.innerCt;
175             innerCt.render(ct);
176
177             this.el = innerCt.getEl();
178
179             //if we're combining subfield errors into a single message, override the markInvalid and clearInvalid
180             //methods of each subfield and show them at the Composite level instead
181             if (this.combineErrors) {
182                 this.eachItem(function(field) {
183                     Ext.apply(field, {
184                         markInvalid : this.onFieldMarkInvalid.createDelegate(this, [field], 0),
185                         clearInvalid: this.onFieldClearInvalid.createDelegate(this, [field], 0)
186                     });
187                 });
188             }
189
190             //set the label 'for' to the first item
191             var l = this.el.parent().parent().child('label', true);
192             if (l) {
193                 l.setAttribute('for', this.items.items[0].id);
194             }
195         }
196
197         Ext.form.CompositeField.superclass.onRender.apply(this, arguments);
198     },
199
200     <div id="method-Ext.form.CompositeField-onFieldMarkInvalid"></div>/**
201      * Called if combineErrors is true and a subfield's markInvalid method is called.
202      * By default this just adds the subfield's error to the internal fieldErrors MixedCollection
203      * @param {Ext.form.Field} field The field that was marked invalid
204      * @param {String} message The error message
205      */
206     onFieldMarkInvalid: function(field, message) {
207         var name  = field.getName(),
208             error = {
209                 field: name, 
210                 errorName: field.fieldLabel || name,
211                 error: message
212             };
213
214         this.fieldErrors.replace(name, error);
215
216         field.el.addClass(field.invalidClass);
217     },
218
219     <div id="method-Ext.form.CompositeField-onFieldClearInvalid"></div>/**
220      * Called if combineErrors is true and a subfield's clearInvalid method is called.
221      * By default this just updates the internal fieldErrors MixedCollection.
222      * @param {Ext.form.Field} field The field that was marked invalid
223      */
224     onFieldClearInvalid: function(field) {
225         this.fieldErrors.removeKey(field.getName());
226
227         field.el.removeClass(field.invalidClass);
228     },
229
230     /**
231      * @private
232      * Called after a subfield is marked valid or invalid, this checks to see if any of the subfields are
233      * currently invalid. If any subfields are invalid it builds a combined error message marks the composite
234      * invalid, otherwise clearInvalid is called
235      */
236     updateInvalidMark: function() {
237         var ieStrict = Ext.isIE6 && Ext.isStrict;
238
239         if (this.fieldErrors.length == 0) {
240             this.clearInvalid();
241
242             //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
243             if (ieStrict) {
244                 this.clearInvalid.defer(50, this);
245             }
246         } else {
247             var message = this.buildCombinedErrorMessage(this.fieldErrors.items);
248
249             this.sortErrors();
250             this.markInvalid(message);
251
252             //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
253             if (ieStrict) {
254                 this.markInvalid(message);
255             }
256         }
257     },
258
259     <div id="method-Ext.form.CompositeField-validateValue"></div>/**
260      * Performs validation checks on each subfield and returns false if any of them fail validation.
261      * @return {Boolean} False if any subfield failed validation
262      */
263     validateValue: function() {
264         var valid = true;
265
266         this.eachItem(function(field) {
267             if (!field.isValid()) valid = false;
268         });
269
270         return valid;
271     },
272
273     <div id="method-Ext.form.CompositeField-buildCombinedErrorMessage"></div>/**
274      * Takes an object containing error messages for contained fields, returning a combined error
275      * string (defaults to just placing each item on a new line). This can be overridden to provide
276      * custom combined error message handling.
277      * @param {Array} errors Array of errors in format: [{field: 'title', error: 'some error'}]
278      * @return {String} The combined error message
279      */
280     buildCombinedErrorMessage: function(errors) {
281         var combined = [],
282             error;
283
284         for (var i = 0, j = errors.length; i < j; i++) {
285             error = errors[i];
286
287             combined.push(String.format("{0}: {1}", error.errorName, error.error));
288         }
289
290         return combined.join("<br />");
291     },
292
293     <div id="method-Ext.form.CompositeField-sortErrors"></div>/**
294      * Sorts the internal fieldErrors MixedCollection by the order in which the fields are defined.
295      * This is called before displaying errors to ensure that the errors are presented in the expected order.
296      * This function can be overridden to provide a custom sorting order if needed.
297      */
298     sortErrors: function() {
299         var fields = this.items;
300
301         this.fieldErrors.sort("ASC", function(a, b) {
302             var findByName = function(key) {
303                 return function(field) {
304                     return field.getName() == key;
305                 };
306             };
307
308             var aIndex = fields.findIndexBy(findByName(a.field)),
309                 bIndex = fields.findIndexBy(findByName(b.field));
310
311             return aIndex < bIndex ? -1 : 1;
312         });
313     },
314
315     <div id="method-Ext.form.CompositeField-reset"></div>/**
316      * Resets each field in the composite to their previous value
317      */
318     reset: function() {
319         this.eachItem(function(item) {
320             item.reset();
321         });
322
323         // Defer the clearInvalid so if BaseForm's collection is being iterated it will be called AFTER it is complete.
324         // Important because reset is being called on both the group and the individual items.
325         (function() {
326             this.clearInvalid();
327         }).defer(50, this);
328     },
329     
330     <div id="method-Ext.form.CompositeField-clearInvalidChildren"></div>/**
331      * Calls clearInvalid on all child fields. This is a convenience function and should not often need to be called
332      * as fields usually take care of clearing themselves
333      */
334     clearInvalidChildren: function() {
335         this.eachItem(function(item) {
336             item.clearInvalid();
337         });
338     },
339
340     <div id="method-Ext.form.CompositeField-buildLabel"></div>/**
341      * Builds a label string from an array of subfield labels.
342      * By default this just joins the labels together with a comma
343      * @param {Array} segments Array of each of the labels in the composite field's subfields
344      * @return {String} The built label
345      */
346     buildLabel: function(segments) {
347         return Ext.clean(segments).join(this.labelConnector);
348     },
349
350     <div id="method-Ext.form.CompositeField-isDirty"></div>/**
351      * Checks each field in the composite and returns true if any is dirty
352      * @return {Boolean} True if any field is dirty
353      */
354     isDirty: function(){
355         //override the behaviour to check sub items.
356         if (this.disabled || !this.rendered) {
357             return false;
358         }
359
360         var dirty = false;
361         this.eachItem(function(item){
362             if(item.isDirty()){
363                 dirty = true;
364                 return false;
365             }
366         });
367         return dirty;
368     },
369
370     /**
371      * @private
372      * Convenience function which passes the given function to every item in the composite
373      * @param {Function} fn The function to call
374      * @param {Object} scope Optional scope object
375      */
376     eachItem: function(fn, scope) {
377         if(this.items && this.items.each){
378             this.items.each(fn, scope || this);
379         }
380     },
381
382     /**
383      * @private
384      * Passes the resize call through to the inner panel
385      */
386     onResize: function(adjWidth, adjHeight, rawWidth, rawHeight) {
387         var innerCt = this.innerCt;
388
389         if (this.rendered && innerCt.rendered) {
390             innerCt.setSize(adjWidth, adjHeight);
391         }
392
393         Ext.form.CompositeField.superclass.onResize.apply(this, arguments);
394     },
395
396     /**
397      * @private
398      * Forces the internal container to be laid out again
399      */
400     doLayout: function(shallow, force) {
401         if (this.rendered) {
402             var innerCt = this.innerCt;
403
404             innerCt.forceLayout = this.ownerCt.forceLayout;
405             innerCt.doLayout(shallow, force);
406         }
407     },
408
409     /**
410      * @private
411      */
412     beforeDestroy: function(){
413         Ext.destroy(this.innerCt);
414
415         Ext.form.CompositeField.superclass.beforeDestroy.call(this);
416     },
417
418     //override the behaviour to check sub items.
419     setReadOnly : function(readOnly) {
420         if (readOnly == undefined) {
421             readOnly = true;
422         }
423         readOnly = !!readOnly;
424
425         if(this.rendered){
426             this.eachItem(function(item){
427                 item.setReadOnly(readOnly);
428             });
429         }
430         this.readOnly = readOnly;
431     },
432
433     onShow : function() {
434         Ext.form.CompositeField.superclass.onShow.call(this);
435         this.doLayout();
436     },
437
438     //override the behaviour to check sub items.
439     onDisable : function(){
440         this.eachItem(function(item){
441             item.disable();
442         });
443     },
444
445     //override the behaviour to check sub items.
446     onEnable : function(){
447         this.eachItem(function(item){
448             item.enable();
449         });
450     }
451 });
452
453 Ext.reg('compositefield', Ext.form.CompositeField);</pre>    
454 </body>
455 </html>