]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/form/FieldContainer.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / form / FieldContainer.js
CommitLineData
6527f429
DM
1/**\r
2 * FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the\r
3 * {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with\r
4 * a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.\r
5 * This is useful for arranging a group of fields or other components within a single item in a form, so\r
6 * that it lines up nicely with other fields. A common use is for grouping a set of related fields under\r
7 * a single label in a form.\r
8 * \r
9 * The container's configured {@link #cfg-items} will be layed out within the field body area according to the\r
10 * configured {@link #layout} type. The default layout is `'autocontainer'`.\r
11 * \r
12 * Like regular fields, FieldContainer can inherit its decoration configuration from the\r
13 * {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,\r
14 * FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}\r
15 * it may itself contain.\r
16 * \r
17 * If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}\r
18 * fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}\r
19 * or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.\r
20 *\r
21 * # Example\r
22 * \r
23 * @example\r
24 * Ext.create('Ext.form.Panel', {\r
25 * title: 'FieldContainer Example',\r
26 * width: 550,\r
27 * bodyPadding: 10,\r
28 * \r
29 * items: [{\r
30 * xtype: 'fieldcontainer',\r
31 * fieldLabel: 'Last Three Jobs',\r
32 * labelWidth: 100,\r
33 * \r
34 * // The body area will contain three text fields, arranged\r
35 * // horizontally, separated by draggable splitters.\r
36 * layout: 'hbox',\r
37 * items: [{\r
38 * xtype: 'textfield',\r
39 * flex: 1\r
40 * }, {\r
41 * xtype: 'splitter'\r
42 * }, {\r
43 * xtype: 'textfield',\r
44 * flex: 1\r
45 * }, {\r
46 * xtype: 'splitter'\r
47 * }, {\r
48 * xtype: 'textfield',\r
49 * flex: 1\r
50 * }]\r
51 * }],\r
52 * renderTo: Ext.getBody()\r
53 * });\r
54 * \r
55 * # Usage of fieldDefaults\r
56 *\r
57 * @example\r
58 * Ext.create('Ext.form.Panel', {\r
59 * title: 'FieldContainer Example',\r
60 * width: 350,\r
61 * bodyPadding: 10,\r
62 * \r
63 * items: [{\r
64 * xtype: 'fieldcontainer',\r
65 * fieldLabel: 'Your Name',\r
66 * labelWidth: 75,\r
67 * defaultType: 'textfield',\r
68 * \r
69 * // Arrange fields vertically, stretched to full width\r
70 * layout: 'anchor',\r
71 * defaults: {\r
72 * layout: '100%'\r
73 * },\r
74 * \r
75 * // These config values will be applied to both sub-fields, except\r
76 * // for Last Name which will use its own msgTarget.\r
77 * fieldDefaults: {\r
78 * msgTarget: 'under',\r
79 * labelAlign: 'top'\r
80 * },\r
81 * \r
82 * items: [{\r
83 * fieldLabel: 'First Name',\r
84 * name: 'firstName'\r
85 * }, {\r
86 * fieldLabel: 'Last Name',\r
87 * name: 'lastName',\r
88 * msgTarget: 'under'\r
89 * }]\r
90 * }],\r
91 * renderTo: Ext.getBody()\r
92 * });\r
93 */\r
94Ext.define('Ext.form.FieldContainer', {\r
95 extend: 'Ext.container.Container',\r
96 mixins: {\r
97 labelable: 'Ext.form.Labelable',\r
98 fieldAncestor: 'Ext.form.FieldAncestor'\r
99 },\r
100 requires: 'Ext.layout.component.field.FieldContainer',\r
101\r
102 alias: 'widget.fieldcontainer',\r
103\r
104 componentLayout: 'fieldcontainer',\r
105\r
106 componentCls: Ext.baseCSSPrefix + 'form-fieldcontainer',\r
107\r
108 shrinkWrap: true,\r
109 \r
110 autoEl: {\r
111 tag: 'div',\r
112 role: 'presentation'\r
113 },\r
114\r
115 childEls: [\r
116 'containerEl'\r
117 ],\r
118\r
119 /**\r
120 * @cfg {Boolean} combineLabels\r
121 * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically\r
122 * generate its label by combining the labels of all the fields it contains. Defaults to false.\r
123 */\r
124 combineLabels: false,\r
125\r
126 //<locale>\r
127 /**\r
128 * @cfg {String} labelConnector\r
129 * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is\r
130 * set to true. Defaults to ', '.\r
131 */\r
132 labelConnector: ', ',\r
133 //</locale>\r
134\r
135 /**\r
136 * @cfg {Boolean} combineErrors\r
137 * If set to true, the field container will automatically combine and display the validation errors from\r
138 * all the fields it contains as a single error on the container, according to the configured\r
139 * {@link #msgTarget}. Defaults to false.\r
140 */\r
141 combineErrors: false,\r
142\r
143 maskOnDisable: false,\r
144 // If we allow this to mark with the invalidCls it will cascade to all\r
145 // child fields, let them handle themselves\r
146 invalidCls: '',\r
147\r
148 fieldSubTpl: [\r
149 '<div id="{id}-containerEl" data-ref="containerEl" class="{containerElCls}"',\r
150 '<tpl if="ariaAttributes">',\r
151 '<tpl foreach="ariaAttributes"> {$}="{.}"</tpl>',\r
152 '<tpl else>',\r
153 ' role="presentation"',\r
154 '</tpl>',\r
155 '>',\r
156 '{%this.renderContainer(out,values)%}',\r
157 '</div>'\r
158 ],\r
159\r
160 initComponent: function() {\r
161 var me = this;\r
162\r
163 // Init mixins\r
164 me.initLabelable();\r
165 me.initFieldAncestor();\r
166 \r
167 me.callParent();\r
168 me.initMonitor();\r
169 },\r
170 \r
171 /**\r
172 * @protected\r
173 * Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.\r
174 * @param {Ext.form.Labelable} labelItem The instance that was added\r
175 */\r
176 onAdd: function(labelItem) {\r
177 var me = this;\r
178\r
179 // Fix for https://sencha.jira.com/browse/EXTJSIV-6424 Which was *sneakily* fixed fixed in version 37\r
180 // In FF < 37, positioning absolutely within a TD positions relative to the TR!\r
181 // So we must add the width of a visible, left-aligned label cell to the x coordinate.\r
182 if (labelItem.isLabelable && Ext.isGecko && Ext.firefoxVersion < 37 && me.layout.type === 'absolute' && !me.hideLabel && me.labelAlign !== 'top') {\r
183 labelItem.x += (me.labelWidth + me.labelPad);\r
184 }\r
185 me.callParent(arguments);\r
186 if (labelItem.isLabelable && me.combineLabels) {\r
187 labelItem.oldHideLabel = labelItem.hideLabel;\r
188 labelItem.hideLabel = true;\r
189 }\r
190 me.updateLabel();\r
191 },\r
192\r
193 /**\r
194 * @protected\r
195 * Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.\r
196 * @param {Ext.form.Labelable} labelItem The instance that was removed\r
197 */\r
198 onRemove: function(labelItem, isDestroying) {\r
199 var me = this;\r
200 me.callParent(arguments);\r
201 if (!isDestroying) {\r
202 if (labelItem.isLabelable && me.combineLabels) {\r
203 labelItem.hideLabel = labelItem.oldHideLabel;\r
204 }\r
205 me.updateLabel();\r
206 } \r
207 },\r
208\r
209 initRenderData: function() {\r
210 var me = this,\r
211 data = me.callParent();\r
212\r
213 data.containerElCls = me.containerElCls;\r
214 data = Ext.applyIf(data, me.getLabelableRenderData());\r
215 data.tipAnchorTarget = me.id + '-containerEl';\r
216 return data;\r
217 },\r
218\r
219 /**\r
220 * Returns the combined field label if {@link #combineLabels} is set to true and if there is no\r
221 * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override\r
222 * this method to provide a custom generated label.\r
223 * @template\r
224 * @return {String} The label, or empty string if none.\r
225 */\r
226 getFieldLabel: function() {\r
227 var label = this.fieldLabel || '';\r
228 if (!label && this.combineLabels) {\r
229 label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {\r
230 return field.getFieldLabel();\r
231 }).join(this.labelConnector);\r
232 }\r
233 return label;\r
234 },\r
235\r
236 getSubTplData: function() {\r
237 var ret = this.initRenderData();\r
238\r
239 Ext.apply(ret, this.subTplData);\r
240 return ret;\r
241 },\r
242\r
243 getSubTplMarkup: function(fieldData) {\r
244 var me = this,\r
245 tpl = me.getTpl('fieldSubTpl'),\r
246 html;\r
247\r
248 if (!tpl.renderContent) {\r
249 me.setupRenderTpl(tpl);\r
250 }\r
251\r
252 html = tpl.apply(me.getSubTplData(fieldData));\r
253 return html;\r
254 },\r
255\r
256 /**\r
257 * @private\r
258 * Updates the content of the labelEl if it is rendered\r
259 */\r
260 updateLabel: function() {\r
261 var me = this,\r
262 label = me.labelEl;\r
263 \r
264 if (label) {\r
265 me.setFieldLabel(me.getFieldLabel());\r
266 }\r
267 },\r
268\r
269\r
270 /**\r
271 * @private\r
272 * Fired when the error message of any field within the container changes, and updates the\r
273 * combined error message to match.\r
274 */\r
275 onFieldErrorChange: function() {\r
276 if (this.combineErrors) {\r
277 var me = this,\r
278 oldError = me.getActiveError(),\r
279 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {\r
280 return field.hasActiveError();\r
281 }),\r
282 newErrors = me.getCombinedErrors(invalidFields);\r
283\r
284 if (newErrors) {\r
285 me.setActiveErrors(newErrors);\r
286 } else {\r
287 me.unsetActiveError();\r
288 }\r
289\r
290 if (oldError !== me.getActiveError()) {\r
291 me.updateLayout();\r
292 }\r
293 }\r
294 },\r
295\r
296 /**\r
297 * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error\r
298 * messages from them. Defaults to prepending each message by the field name and a colon. This\r
299 * can be overridden to provide custom combined error message handling, for instance changing\r
300 * the format of each message or sorting the array (it is sorted in order of appearance by default).\r
301 * @param {Ext.form.field.Field[]} invalidFields An Array of the sub-fields which are currently invalid.\r
302 * @return {String[]} The combined list of error messages\r
303 */\r
304 getCombinedErrors: function(invalidFields) {\r
305 var errors = [],\r
306 f,\r
307 fLen = invalidFields.length,\r
308 field,\r
309 activeErrors, a, aLen,\r
310 error, label;\r
311\r
312 for (f = 0; f < fLen; f++) {\r
313 field = invalidFields[f];\r
314 activeErrors = field.getActiveErrors();\r
315 aLen = activeErrors.length;\r
316\r
317 for (a = 0; a < aLen; a++) {\r
318 error = activeErrors[a];\r
319 label = field.getFieldLabel();\r
320\r
321 errors.push((label ? label + ': ' : '') + error);\r
322 }\r
323 }\r
324\r
325 return errors;\r
326 },\r
327\r
328 privates: {\r
329 applyTargetCls: function(targetCls) {\r
330 var containerElCls = this.containerElCls;\r
331\r
332 this.containerElCls = containerElCls ? containerElCls + ' ' + targetCls : targetCls;\r
333 },\r
334\r
335 getTargetEl: function() {\r
336 return this.containerEl;\r
337 },\r
338\r
339 initRenderTpl: function() {\r
340 var me = this;\r
341 if (!me.hasOwnProperty('renderTpl')) {\r
342 me.renderTpl = me.getTpl('labelableRenderTpl');\r
343 }\r
344 return me.callParent();\r
345 }\r
346 }\r
347});\r