Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / form / field / Checkbox.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  * @docauthor Robert Dougan <rob@sencha.com>
17  *
18  * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
19  * parent class for {@link Ext.form.field.Radio radio buttons}.
20  *
21  * # Labeling
22  *
23  * In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
24  * may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
25  * {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
26  *
27  * # Values
28  *
29  * The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
30  * The following values will check the checkbox:
31  *
32  * - `true`
33  * - `'true'`
34  * - `'1'`
35  * - `'on'`
36  *
37  * Any other value will uncheck the checkbox.
38  *
39  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
40  * sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
41  * this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
42  * will be used.
43  *
44  * # Example usage
45  *
46  *     @example
47  *     Ext.create('Ext.form.Panel', {
48  *         bodyPadding: 10,
49  *         width: 300,
50  *         title: 'Pizza Order',
51  *         items: [
52  *             {
53  *                 xtype: 'fieldcontainer',
54  *                 fieldLabel: 'Toppings',
55  *                 defaultType: 'checkboxfield',
56  *                 items: [
57  *                     {
58  *                         boxLabel  : 'Anchovies',
59  *                         name      : 'topping',
60  *                         inputValue: '1',
61  *                         id        : 'checkbox1'
62  *                     }, {
63  *                         boxLabel  : 'Artichoke Hearts',
64  *                         name      : 'topping',
65  *                         inputValue: '2',
66  *                         checked   : true,
67  *                         id        : 'checkbox2'
68  *                     }, {
69  *                         boxLabel  : 'Bacon',
70  *                         name      : 'topping',
71  *                         inputValue: '3',
72  *                         id        : 'checkbox3'
73  *                     }
74  *                 ]
75  *             }
76  *         ],
77  *         bbar: [
78  *             {
79  *                 text: 'Select Bacon',
80  *                 handler: function() {
81  *                     Ext.getCmp('checkbox3').setValue(true);
82  *                 }
83  *             },
84  *             '-',
85  *             {
86  *                 text: 'Select All',
87  *                 handler: function() {
88  *                     Ext.getCmp('checkbox1').setValue(true);
89  *                     Ext.getCmp('checkbox2').setValue(true);
90  *                     Ext.getCmp('checkbox3').setValue(true);
91  *                 }
92  *             },
93  *             {
94  *                 text: 'Deselect All',
95  *                 handler: function() {
96  *                     Ext.getCmp('checkbox1').setValue(false);
97  *                     Ext.getCmp('checkbox2').setValue(false);
98  *                     Ext.getCmp('checkbox3').setValue(false);
99  *                 }
100  *             }
101  *         ],
102  *         renderTo: Ext.getBody()
103  *     });
104  */
105 Ext.define('Ext.form.field.Checkbox', {
106     extend: 'Ext.form.field.Base',
107     alias: ['widget.checkboxfield', 'widget.checkbox'],
108     alternateClassName: 'Ext.form.Checkbox',
109     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
110
111     // note: {id} here is really {inputId}, but {cmpId} is available
112     fieldSubTpl: [
113         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
114             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
115         '</tpl>',
116         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
117         // styled with a custom checkbox image. This allows greater control and consistency in
118         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
119         '<input type="button" id="{id}" ',
120             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
121             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
122         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
123             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
124         '</tpl>',
125         {
126             disableFormats: true,
127             compiled: true
128         }
129     ],
130
131     isCheckbox: true,
132
133     /**
134      * @cfg {String} [focusCls='x-form-cb-focus']
135      * The CSS class to use when the checkbox receives focus
136      */
137     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
138
139     /**
140      * @cfg {String} [fieldCls='x-form-field']
141      * The default CSS class for the checkbox
142      */
143
144     /**
145      * @cfg {String} [fieldBodyCls='x-form-cb-wrap']
146      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
147      * .
148      */
149     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
150
151     /**
152      * @cfg {Boolean} checked
153      * true if the checkbox should render initially checked
154      */
155     checked: false,
156
157     /**
158      * @cfg {String} [checkedCls='x-form-cb-checked']
159      * The CSS class added to the component's main element when it is in the checked state.
160      */
161     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
162
163     /**
164      * @cfg {String} boxLabel
165      * An optional text label that will appear next to the checkbox. Whether it appears before or after the checkbox is
166      * determined by the {@link #boxLabelAlign} config.
167      */
168
169     /**
170      * @cfg {String} [boxLabelCls='x-form-cb-label']
171      * The CSS class to be applied to the {@link #boxLabel} element
172      */
173     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
174
175     /**
176      * @cfg {String} boxLabelAlign
177      * The position relative to the checkbox where the {@link #boxLabel} should appear. Recognized values are 'before'
178      * and 'after'.
179      */
180     boxLabelAlign: 'after',
181
182     /**
183      * @cfg {String} inputValue
184      * The value that should go into the generated input element's value attribute and should be used as the parameter
185      * value when submitting as part of a form.
186      */
187     inputValue: 'on',
188
189     /**
190      * @cfg {String} uncheckedValue
191      * If configured, this will be submitted as the checkbox's value during form submit if the checkbox is unchecked. By
192      * default this is undefined, which results in nothing being submitted for the checkbox field when the form is
193      * submitted (the default behavior of HTML checkboxes).
194      */
195
196     /**
197      * @cfg {Function} handler
198      * A function called when the {@link #checked} value changes (can be used instead of handling the {@link #change
199      * change event}).
200      * @cfg {Ext.form.field.Checkbox} handler.checkbox The Checkbox being toggled.
201      * @cfg {Boolean} handler.checked The new checked state of the checkbox.
202      */
203
204     /**
205      * @cfg {Object} scope
206      * An object to use as the scope ('this' reference) of the {@link #handler} function (defaults to this Checkbox).
207      */
208
209     // private overrides
210     checkChangeEvents: [],
211     inputType: 'checkbox',
212     ariaRole: 'checkbox',
213
214     // private
215     onRe: /^on$/i,
216
217     initComponent: function(){
218         this.callParent(arguments);
219         this.getManager().add(this);
220     },
221
222     initValue: function() {
223         var me = this,
224             checked = !!me.checked;
225
226         /**
227          * @property {Object} originalValue
228          * The original value of the field as configured in the {@link #checked} configuration, or as loaded by the last
229          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
230          */
231         me.originalValue = me.lastValue = checked;
232
233         // Set the initial checked state
234         me.setValue(checked);
235     },
236
237     // private
238     onRender : function(ct, position) {
239         var me = this;
240
241         /**
242          * @property {Ext.Element} boxLabelEl
243          * A reference to the label element created for the {@link #boxLabel}. Only present if the component has been
244          * rendered and has a boxLabel configured.
245          */
246         me.addChildEls('boxLabelEl');
247
248         Ext.applyIf(me.subTplData, {
249             boxLabel: me.boxLabel,
250             boxLabelCls: me.boxLabelCls,
251             boxLabelAlign: me.boxLabelAlign
252         });
253
254         me.callParent(arguments);
255     },
256
257     initEvents: function() {
258         var me = this;
259         me.callParent();
260         me.mon(me.inputEl, 'click', me.onBoxClick, me);
261     },
262
263     /**
264      * @private Handle click on the checkbox button
265      */
266     onBoxClick: function(e) {
267         var me = this;
268         if (!me.disabled && !me.readOnly) {
269             this.setValue(!this.checked);
270         }
271     },
272
273     /**
274      * Returns the checked state of the checkbox.
275      * @return {Boolean} True if checked, else false
276      */
277     getRawValue: function() {
278         return this.checked;
279     },
280
281     /**
282      * Returns the checked state of the checkbox.
283      * @return {Boolean} True if checked, else false
284      */
285     getValue: function() {
286         return this.checked;
287     },
288
289     /**
290      * Returns the submit value for the checkbox which can be used when submitting forms.
291      * @return {Boolean/Object} True if checked; otherwise either the {@link #uncheckedValue} or null.
292      */
293     getSubmitValue: function() {
294         var unchecked = this.uncheckedValue,
295             uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
296         return this.checked ? this.inputValue : uncheckedVal;
297     },
298
299     /**
300      * Sets the checked state of the checkbox.
301      *
302      * @param {Boolean/String/Number} value The following values will check the checkbox:
303      * `true, 'true', '1', 1, or 'on'`, as well as a String that matches the {@link #inputValue}.
304      * Any other value will uncheck the checkbox.
305      * @return {Boolean} the new checked state of the checkbox
306      */
307     setRawValue: function(value) {
308         var me = this,
309             inputEl = me.inputEl,
310             inputValue = me.inputValue,
311             checked = (value === true || value === 'true' || value === '1' || value === 1 ||
312                 (((Ext.isString(value) || Ext.isNumber(value)) && inputValue) ? value == inputValue : me.onRe.test(value)));
313
314         if (inputEl) {
315             inputEl.dom.setAttribute('aria-checked', checked);
316             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
317         }
318
319         me.checked = me.rawValue = checked;
320         return checked;
321     },
322
323     /**
324      * Sets the checked state of the checkbox, and invokes change detection.
325      * @param {Boolean/String} checked The following values will check the checkbox: `true, 'true', '1', or 'on'`, as
326      * well as a String that matches the {@link #inputValue}. Any other value will uncheck the checkbox.
327      * @return {Ext.form.field.Checkbox} this
328      */
329     setValue: function(checked) {
330         var me = this;
331
332         // If an array of strings is passed, find all checkboxes in the group with the same name as this
333         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
334         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
335         // don't want users depending on this behavior.
336         if (Ext.isArray(checked)) {
337             me.getManager().getByName(me.name).each(function(cb) {
338                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
339             });
340         } else {
341             me.callParent(arguments);
342         }
343
344         return me;
345     },
346
347     // private
348     valueToRaw: function(value) {
349         // No extra conversion for checkboxes
350         return value;
351     },
352
353     /**
354      * @private
355      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
356      * function if specified.
357      */
358     onChange: function(newVal, oldVal) {
359         var me = this,
360             handler = me.handler;
361         if (handler) {
362             handler.call(me.scope || me, me, newVal);
363         }
364         me.callParent(arguments);
365     },
366
367     // inherit docs
368     beforeDestroy: function(){
369         this.callParent();
370         this.getManager().removeAtKey(this.id);
371     },
372
373     // inherit docs
374     getManager: function() {
375         return Ext.form.CheckboxManager;
376     },
377
378     onEnable: function() {
379         var me = this,
380             inputEl = me.inputEl;
381         me.callParent();
382         if (inputEl) {
383             // Can still be disabled if the field is readOnly
384             inputEl.dom.disabled = me.readOnly;
385         }
386     },
387
388     setReadOnly: function(readOnly) {
389         var me = this,
390             inputEl = me.inputEl;
391         if (inputEl) {
392             // Set the button to disabled when readonly
393             inputEl.dom.disabled = readOnly || me.disabled;
394         }
395         me.readOnly = readOnly;
396     },
397
398     // Calculates and returns the natural width of the bodyEl. It's possible that the initial rendering will
399     // cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping while measuring.
400     getBodyNaturalWidth: function() {
401         var me = this,
402             bodyEl = me.bodyEl,
403             ws = 'white-space',
404             width;
405         bodyEl.setStyle(ws, 'nowrap');
406         width = bodyEl.getWidth();
407         bodyEl.setStyle(ws, '');
408         return width;
409     }
410
411 });
412