Upgrade to ExtJS 3.2.1 - Released 04/27/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.2.1
11  * Copyright(c) 2006-2010 Ext JS, Inc.
12  * licensing@extjs.com
13  * http://www.extjs.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     //inherit docs
85     //Builds the composite field label
86     initComponent: function() {
87         var labels = [],
88             items  = this.items,
89             item;
90
91         for (var i=0, j = items.length; i < j; i++) {
92             item = items[i];
93
94             labels.push(item.fieldLabel);
95
96             //apply any defaults
97             Ext.apply(item, this.defaults);
98
99             //apply default margins to each item except the last
100             if (!(i == j - 1 && this.skipLastItemMargin)) {
101                 Ext.applyIf(item, {margins: this.defaultMargins});
102             }
103         }
104
105         this.fieldLabel = this.fieldLabel || this.buildLabel(labels);
106
107         <div id="prop-Ext.form.CompositeField-fieldErrors"></div>/**
108          * @property fieldErrors
109          * @type Ext.util.MixedCollection
110          * MixedCollection of current errors on the Composite's subfields. This is used internally to track when
111          * to show and hide error messages at the Composite level. Listeners are attached to the MixedCollection's
112          * add, remove and replace events to update the error icon in the UI as errors are added or removed.
113          */
114         this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
115             return item.field;
116         });
117
118         this.fieldErrors.on({
119             scope  : this,
120             add    : this.updateInvalidMark,
121             remove : this.updateInvalidMark,
122             replace: this.updateInvalidMark
123         });
124
125         Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);
126     },
127
128     /**
129      * @private
130      * Creates an internal container using hbox and renders the fields to it
131      */
132     onRender: function(ct, position) {
133         if (!this.el) {
134             <div id="prop-Ext.form.CompositeField-innerCt"></div>/**
135              * @property innerCt
136              * @type Ext.Container
137              * A container configured with hbox layout which is responsible for laying out the subfields
138              */
139             var innerCt = this.innerCt = new Ext.Container({
140                 layout  : 'hbox',
141                 renderTo: ct,
142                 items   : this.items,
143                 cls     : 'x-form-composite',
144                 defaultMargins: '0 3 0 0'
145             });
146
147             this.el = innerCt.getEl();
148
149             var fields = 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             //if we're combining subfield errors into a single message, override the markInvalid and clearInvalid
162             //methods of each subfield and show them at the Composite level instead
163             if (this.combineErrors) {
164                 this.eachItem(function(field) {
165                     Ext.apply(field, {
166                         markInvalid : this.onFieldMarkInvalid.createDelegate(this, [field], 0),
167                         clearInvalid: this.onFieldClearInvalid.createDelegate(this, [field], 0)
168                     });
169                 });
170             }
171
172             //set the label 'for' to the first item
173             var l = this.el.parent().parent().child('label', true);
174             if (l) {
175                 l.setAttribute('for', this.items.items[0].id);
176             }
177         }
178
179         Ext.form.CompositeField.superclass.onRender.apply(this, arguments);
180     },
181
182     <div id="method-Ext.form.CompositeField-onFieldMarkInvalid"></div>/**
183      * Called if combineErrors is true and a subfield's markInvalid method is called.
184      * By default this just adds the subfield's error to the internal fieldErrors MixedCollection
185      * @param {Ext.form.Field} field The field that was marked invalid
186      * @param {String} message The error message
187      */
188     onFieldMarkInvalid: function(field, message) {
189         var name  = field.getName(),
190             error = {field: name, error: message};
191
192         this.fieldErrors.replace(name, error);
193
194         field.el.addClass(field.invalidClass);
195     },
196
197     <div id="method-Ext.form.CompositeField-onFieldClearInvalid"></div>/**
198      * Called if combineErrors is true and a subfield's clearInvalid method is called.
199      * By default this just updates the internal fieldErrors MixedCollection.
200      * @param {Ext.form.Field} field The field that was marked invalid
201      */
202     onFieldClearInvalid: function(field) {
203         this.fieldErrors.removeKey(field.getName());
204
205         field.el.removeClass(field.invalidClass);
206     },
207
208     /**
209      * @private
210      * Called after a subfield is marked valid or invalid, this checks to see if any of the subfields are
211      * currently invalid. If any subfields are invalid it builds a combined error message marks the composite
212      * invalid, otherwise clearInvalid is called
213      */
214     updateInvalidMark: function() {
215         var ieStrict = Ext.isIE6 && Ext.isStrict;
216
217         if (this.fieldErrors.length == 0) {
218             this.clearInvalid();
219
220             //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
221             if (ieStrict) {
222                 this.clearInvalid.defer(50, this);
223             }
224         } else {
225             var message = this.buildCombinedErrorMessage(this.fieldErrors.items);
226
227             this.sortErrors();
228             this.markInvalid(message);
229
230             //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
231             if (ieStrict) {
232                 this.markInvalid(message);
233             }
234         }
235     },
236
237     <div id="method-Ext.form.CompositeField-validateValue"></div>/**
238      * Performs validation checks on each subfield and returns false if any of them fail validation.
239      * @return {Boolean} False if any subfield failed validation
240      */
241     validateValue: function() {
242         var valid = true;
243
244         this.eachItem(function(field) {
245             if (!field.isValid()) valid = false;
246         });
247
248         return valid;
249     },
250
251     <div id="method-Ext.form.CompositeField-buildCombinedErrorMessage"></div>/**
252      * Takes an object containing error messages for contained fields, returning a combined error
253      * string (defaults to just placing each item on a new line). This can be overridden to provide
254      * custom combined error message handling.
255      * @param {Array} errors Array of errors in format: [{field: 'title', error: 'some error'}]
256      * @return {String} The combined error message
257      */
258     buildCombinedErrorMessage: function(errors) {
259         var combined = [],
260             error;
261
262         for (var i = 0, j = errors.length; i < j; i++) {
263             error = errors[i];
264
265             combined.push(String.format("{0}: {1}", error.field, error.error));
266         }
267
268         return combined.join("<br />");
269     },
270
271     <div id="method-Ext.form.CompositeField-sortErrors"></div>/**
272      * Sorts the internal fieldErrors MixedCollection by the order in which the fields are defined.
273      * This is called before displaying errors to ensure that the errors are presented in the expected order.
274      * This function can be overridden to provide a custom sorting order if needed.
275      */
276     sortErrors: function() {
277         var fields = this.items;
278
279         this.fieldErrors.sort("ASC", function(a, b) {
280             var findByName = function(key) {
281                 return function(field) {
282                     return field.getName() == key;
283                 };
284             };
285
286             var aIndex = fields.findIndexBy(findByName(a.field)),
287                 bIndex = fields.findIndexBy(findByName(b.field));
288
289             return aIndex < bIndex ? -1 : 1;
290         });
291     },
292
293     <div id="method-Ext.form.CompositeField-reset"></div>/**
294      * Resets each field in the composite to their previous value
295      */
296     reset: function() {
297         this.eachItem(function(item) {
298             item.reset();
299         });
300
301         // Defer the clearInvalid so if BaseForm's collection is being iterated it will be called AFTER it is complete.
302         // Important because reset is being called on both the group and the individual items.
303         (function() {
304             this.clearInvalid();
305         }).defer(50, this);
306     },
307     
308     <div id="method-Ext.form.CompositeField-clearInvalidChildren"></div>/**
309      * Calls clearInvalid on all child fields. This is a convenience function and should not often need to be called
310      * as fields usually take care of clearing themselves
311      */
312     clearInvalidChildren: function() {
313         this.eachItem(function(item) {
314             item.clearInvalid();
315         });
316     },
317
318     <div id="method-Ext.form.CompositeField-buildLabel"></div>/**
319      * Builds a label string from an array of subfield labels.
320      * By default this just joins the labels together with a comma
321      * @param {Array} segments Array of each of the labels in the composite field's subfields
322      * @return {String} The built label
323      */
324     buildLabel: function(segments) {
325         return segments.join(", ");
326     },
327
328     <div id="method-Ext.form.CompositeField-isDirty"></div>/**
329      * Checks each field in the composite and returns true if any is dirty
330      * @return {Boolean} True if any field is dirty
331      */
332     isDirty: function(){
333         //override the behaviour to check sub items.
334         if (this.disabled || !this.rendered) {
335             return false;
336         }
337
338         var dirty = false;
339         this.eachItem(function(item){
340             if(item.isDirty()){
341                 dirty = true;
342                 return false;
343             }
344         });
345         return dirty;
346     },
347
348     /**
349      * @private
350      * Convenience function which passes the given function to every item in the composite
351      * @param {Function} fn The function to call
352      * @param {Object} scope Optional scope object
353      */
354     eachItem: function(fn, scope) {
355         if(this.items && this.items.each){
356             this.items.each(fn, scope || this);
357         }
358     },
359
360     /**
361      * @private
362      * Passes the resize call through to the inner panel
363      */
364     onResize: function(adjWidth, adjHeight, rawWidth, rawHeight) {
365         var innerCt = this.innerCt;
366
367         if (this.rendered && innerCt.rendered) {
368             innerCt.setSize(adjWidth, adjHeight);
369         }
370
371         Ext.form.CompositeField.superclass.onResize.apply(this, arguments);
372     },
373
374     /**
375      * @private
376      * Forces the internal container to be laid out again
377      */
378     doLayout: function(shallow, force) {
379         if (this.rendered) {
380             var innerCt = this.innerCt;
381
382             innerCt.forceLayout = this.ownerCt.forceLayout;
383             innerCt.doLayout(shallow, force);
384         }
385     },
386
387     /**
388      * @private
389      */
390     beforeDestroy: function(){
391         Ext.destroy(this.innerCt);
392
393         Ext.form.CompositeField.superclass.beforeDestroy.call(this);
394     },
395
396     //override the behaviour to check sub items.
397     setReadOnly : function(readOnly) {
398         readOnly = readOnly || true;
399
400         if(this.rendered){
401             this.eachItem(function(item){
402                 item.setReadOnly(readOnly);
403             });
404         }
405         this.readOnly = readOnly;
406     },
407
408     onShow : function() {
409         Ext.form.CompositeField.superclass.onShow.call(this);
410         this.doLayout();
411     },
412
413     //override the behaviour to check sub items.
414     onDisable : function(){
415         this.eachItem(function(item){
416             item.disable();
417         });
418     },
419
420     //override the behaviour to check sub items.
421     onEnable : function(){
422         this.eachItem(function(item){
423             item.enable();
424         });
425     }
426 });
427
428 Ext.reg('compositefield', Ext.form.CompositeField);
429 </pre>    
430 </body>
431 </html>