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