]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging\r | |
3 | * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field}\r | |
4 | * methods for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the\r | |
5 | * group of radio buttons as a whole.\r | |
6 | *\r | |
7 | * # Validation\r | |
8 | *\r | |
9 | * Individual radio buttons themselves have no default validation behavior, but\r | |
10 | * sometimes you want to require a user to select one of a group of radios. RadioGroup\r | |
11 | * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at\r | |
12 | * one of the radio buttons, the entire group will be highlighted as invalid and the\r | |
13 | * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.\r | |
14 | *\r | |
15 | * # Layout\r | |
16 | *\r | |
17 | * The default layout for RadioGroup makes it easy to arrange the radio buttons into\r | |
18 | * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also\r | |
19 | * use a completely different layout by setting the {@link #cfg-layout} to one of the \r | |
20 | * other supported layout types; for instance you may wish to use a custom arrangement \r | |
21 | * of hbox and vbox containers. In that case the Radio components at any depth will \r | |
22 | * still be managed by the RadioGroup's validation.\r | |
23 | *\r | |
24 | * # Example usage\r | |
25 | *\r | |
26 | * @example\r | |
27 | * Ext.create('Ext.form.Panel', {\r | |
28 | * title: 'RadioGroup Example',\r | |
29 | * width: 300,\r | |
30 | * height: 125,\r | |
31 | * bodyPadding: 10,\r | |
32 | * renderTo: Ext.getBody(),\r | |
33 | * items:[{\r | |
34 | * xtype: 'radiogroup',\r | |
35 | * fieldLabel: 'Two Columns',\r | |
36 | * // Arrange radio buttons into two columns, distributed vertically\r | |
37 | * columns: 2,\r | |
38 | * vertical: true,\r | |
39 | * items: [\r | |
40 | * { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },\r | |
41 | * { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},\r | |
42 | * { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },\r | |
43 | * { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },\r | |
44 | * { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },\r | |
45 | * { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }\r | |
46 | * ]\r | |
47 | * }]\r | |
48 | * });\r | |
49 | *\r | |
50 | */\r | |
51 | Ext.define('Ext.form.RadioGroup', {\r | |
52 | extend: 'Ext.form.CheckboxGroup',\r | |
53 | alias: 'widget.radiogroup',\r | |
54 | \r | |
55 | requires: [\r | |
56 | 'Ext.form.field.Radio'\r | |
57 | ],\r | |
58 | \r | |
59 | mixins: [\r | |
60 | 'Ext.util.FocusableContainer'\r | |
61 | ],\r | |
62 | \r | |
63 | /**\r | |
64 | * @cfg {Ext.form.field.Radio[]/Object[]} items\r | |
65 | * An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange in the group.\r | |
66 | */\r | |
67 | /**\r | |
68 | * @cfg {Boolean} allowBlank\r | |
69 | * True to allow every item in the group to be blank.\r | |
70 | * If allowBlank = false and no items are selected at validation time, {@link #blankText} will\r | |
71 | * be used as the error text.\r | |
72 | */\r | |
73 | allowBlank : true,\r | |
74 | //<locale>\r | |
75 | /**\r | |
76 | * @cfg {String} blankText\r | |
77 | * Error text to display if the {@link #allowBlank} validation fails\r | |
78 | */\r | |
79 | blankText : 'You must select one item in this group',\r | |
80 | //</locale>\r | |
81 | \r | |
82 | defaultType : 'radiofield',\r | |
83 | \r | |
84 | /**\r | |
85 | * @cfg {Boolean} [local=false]\r | |
86 | * By default, child {@link Ext.form.field.Radio radio} `name`s are scoped to the encapsulating {@link Ext.form.Panel form panel}\r | |
87 | * if any, of the document.\r | |
88 | *\r | |
89 | * If you are using multiple `RadioGroup`s each of which uses the same `name` configuration in child {@link Ext.form.field.Radio radio}s, configure this as `true` to scope\r | |
90 | * the names to within this `RadioGroup`\r | |
91 | */\r | |
92 | local: false,\r | |
93 | \r | |
94 | defaultBindProperty: 'value',\r | |
95 | \r | |
96 | /**\r | |
97 | * @private\r | |
98 | */\r | |
99 | groupCls : Ext.baseCSSPrefix + 'form-radio-group',\r | |
100 | \r | |
101 | ariaRole: 'radiogroup',\r | |
102 | \r | |
103 | initRenderData: function() {\r | |
104 | var me = this,\r | |
105 | data, ariaAttr;\r | |
106 | \r | |
107 | data = me.callParent();\r | |
108 | ariaAttr = data.ariaAttributes;\r | |
109 | \r | |
110 | if (ariaAttr) {\r | |
111 | ariaAttr['aria-required'] = !me.allowBlank;\r | |
112 | ariaAttr['aria-invalid'] = false;\r | |
113 | }\r | |
114 | \r | |
115 | return data;\r | |
116 | },\r | |
117 | \r | |
118 | lookupComponent: function(config) {\r | |
119 | var result = this.callParent([config]);\r | |
120 | \r | |
121 | // Local means that the exclusivity of checking by name is scoped to this RadioGroup.\r | |
122 | // So multiple RadioGroups can be used which use the same Radio names.\r | |
123 | // This enables their use as a grid widget.\r | |
124 | if (this.local) {\r | |
125 | result.formId = this.getId();\r | |
126 | }\r | |
127 | return result;\r | |
128 | },\r | |
129 | \r | |
130 | getBoxes: function(query, root) {\r | |
131 | return (root || this).query('[isRadio]' + (query||''));\r | |
132 | },\r | |
133 | \r | |
134 | checkChange: function() {\r | |
135 | var value = this.getValue(),\r | |
136 | key = Ext.Object.getKeys(value)[0];\r | |
137 | \r | |
138 | // If the value is an array we skip out here because it's during a change\r | |
139 | // between multiple items, so we never want to fire a change\r | |
140 | if (Ext.isArray(value[key])) {\r | |
141 | return;\r | |
142 | }\r | |
143 | this.callParent(arguments); \r | |
144 | },\r | |
145 | \r | |
146 | /**\r | |
147 | * Sets the value of the radio group. The radio with corresponding name and value will be set.\r | |
148 | * This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value is allowed\r | |
149 | * for each name. You can use the setValue method as:\r | |
150 | *\r | |
151 | * var form = Ext.create('Ext.form.Panel', {\r | |
152 | * title : 'RadioGroup Example',\r | |
153 | * width : 300,\r | |
154 | * bodyPadding : 10,\r | |
155 | * renderTo : Ext.getBody(),\r | |
156 | * items : [\r | |
157 | * {\r | |
158 | * xtype : 'radiogroup',\r | |
159 | * fieldLabel : 'Group',\r | |
160 | * items : [\r | |
161 | * { boxLabel : 'Item 1', name : 'rb', inputValue : 1 },\r | |
162 | * { boxLabel : 'Item 2', name : 'rb', inputValue : 2 }\r | |
163 | * ]\r | |
164 | * }\r | |
165 | * ],\r | |
166 | * tbar : [\r | |
167 | * {\r | |
168 | * text : 'setValue on RadioGroup',\r | |
169 | * handler : function () {\r | |
170 | * form.child('radiogroup').setValue({\r | |
171 | * rb : 2\r | |
172 | * });\r | |
173 | * }\r | |
174 | * }\r | |
175 | * ]\r | |
176 | * });\r | |
177 | *\r | |
178 | * @param {Object} value The map from names to values to be set.\r | |
179 | * @return {Ext.form.RadioGroup} this\r | |
180 | */\r | |
181 | setValue: function(value) {\r | |
182 | var cbValue, first, formId, radios,\r | |
183 | i, len, name;\r | |
184 | \r | |
185 | if (Ext.isObject(value)) {\r | |
186 | Ext.suspendLayouts();\r | |
187 | first = this.items.first();\r | |
188 | formId = first ? first.getFormId() : null;\r | |
189 | \r | |
190 | for (name in value) {\r | |
191 | cbValue = value[name];\r | |
192 | radios = Ext.form.RadioManager.getWithValue(name, cbValue, formId).items;\r | |
193 | len = radios.length;\r | |
194 | \r | |
195 | for (i = 0; i < len; ++i) {\r | |
196 | radios[i].setValue(true);\r | |
197 | }\r | |
198 | }\r | |
199 | Ext.resumeLayouts(true);\r | |
200 | }\r | |
201 | return this;\r | |
202 | },\r | |
203 | \r | |
204 | markInvalid: function(errors) {\r | |
205 | var ariaDom = this.ariaEl.dom;\r | |
206 | \r | |
207 | this.callParent([errors]);\r | |
208 | \r | |
209 | if (ariaDom){\r | |
210 | ariaDom.setAttribute('aria-invalid', true);\r | |
211 | }\r | |
212 | },\r | |
213 | \r | |
214 | clearInvalid: function() {\r | |
215 | var ariaDom = this.ariaEl.dom;\r | |
216 | \r | |
217 | this.callParent();\r | |
218 | \r | |
219 | if (ariaDom) {\r | |
220 | ariaDom.setAttribute('aria-invalid', false);\r | |
221 | }\r | |
222 | },\r | |
223 | \r | |
224 | privates: {\r | |
225 | getFocusables: function() {\r | |
226 | return this.getBoxes();\r | |
227 | },\r | |
228 | \r | |
229 | initDefaultFocusable: function(beforeRender) {\r | |
230 | var me = this,\r | |
231 | checked, item;\r | |
232 | \r | |
233 | checked = me.getChecked();\r | |
234 | \r | |
235 | // In a Radio group, only one button is supposed to be checked\r | |
236 | //<debug>\r | |
237 | if (checked.length > 1) {\r | |
238 | Ext.log.error("RadioGroup " + me.id + " has more than one checked button");\r | |
239 | }\r | |
240 | //</debug>\r | |
241 | \r | |
242 | // If we have a checked button, it gets the initial childTabIndex,\r | |
243 | // otherwise the first button gets it\r | |
244 | if (checked.length) {\r | |
245 | item = checked[0];\r | |
246 | }\r | |
247 | else {\r | |
248 | item = me.findNextFocusableChild({\r | |
249 | beforeRender: beforeRender,\r | |
250 | step: 1\r | |
251 | });\r | |
252 | }\r | |
253 | \r | |
254 | if (item) {\r | |
255 | me.activateFocusable(item);\r | |
256 | }\r | |
257 | \r | |
258 | return item;\r | |
259 | },\r | |
260 | \r | |
261 | getFocusableContainerEl: function() {\r | |
262 | return this.containerEl;\r | |
263 | },\r | |
264 | \r | |
265 | onFocusableContainerFocusLeave: function() {\r | |
266 | this.clearFocusables();\r | |
267 | this.initDefaultFocusable();\r | |
268 | },\r | |
269 | \r | |
270 | doFocusableChildAdd: function(child) {\r | |
271 | var me = this,\r | |
272 | mixin = me.mixins.focusablecontainer,\r | |
273 | boxes, i, len;\r | |
274 | \r | |
275 | boxes = child.isContainer ? me.getBoxes('', child) : [child];\r | |
276 | \r | |
277 | for (i = 0, len = boxes.length; i < len; i++) {\r | |
278 | mixin.doFocusableChildAdd.call(me, boxes[i]);\r | |
279 | }\r | |
280 | },\r | |
281 | \r | |
282 | doFocusableChildRemove: function(child) {\r | |
283 | var me = this,\r | |
284 | mixin = me.mixins.focusablecontainer,\r | |
285 | boxes, i, len;\r | |
286 | \r | |
287 | boxes = child.isContainer ? me.getBoxes('', child) : [child];\r | |
288 | \r | |
289 | for (i = 0, len = boxes.length; i < len; i++) {\r | |
290 | mixin.doFocusableChildRemove.call(me, boxes[i]);\r | |
291 | }\r | |
292 | },\r | |
293 | \r | |
294 | focusChild: function(radio, forward, e) {\r | |
295 | var nextRadio = this.mixins.focusablecontainer.focusChild.apply(this, arguments);\r | |
296 | \r | |
297 | // Ctrl-arrow does not select the radio that is going to be focused\r | |
298 | if (!e.ctrlKey) {\r | |
299 | nextRadio.setValue(true);\r | |
300 | }\r | |
301 | }\r | |
302 | }\r | |
303 | });\r |