PDA

View Full Version : Themed checkboxes/radios for Ext 3.0



Condor
4 Apr 2009, 4:18 AM
Just as we were getting used to the new themed checkboxes/radios in Ext 2.2+, Jack removed them from Ext 3.0.

Because Ext 2.2+ has all kinds of problems with checkboxes and radios I decided to start from scratch and write all-new themed checkbox/radio support for Ext 3.0:

Include the following code after loading ext-all.js:

Ext.override(Ext.form.Checkbox, {
checked: undefined,
actionMode: 'wrap',
innerCls: 'x-form-checkbox-inner',
onResize: function(){
Ext.form.Checkbox.superclass.onResize.apply(this, arguments);
if(!this.boxLabel){
this.innerWrap.alignTo(this.wrap, 'c-c');
}
},
initEvents: function(){
Ext.form.Checkbox.superclass.initEvents.call(this);
this.mon(this.el, {
click: this.onClick,
change: this.onClick,
mouseenter: this.onMouseEnter,
mouseleave: this.onMouseLeave,
mousedown: this.onMouseDown,
mouseup: this.onMouseUp,
scope: this
});
},
onRender: function(ct, position){
Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
if(this.inputValue !== undefined){
this.el.dom.value = this.inputValue;
}else{
this.inputValue = this.el.dom.value;
}
this.innerWrap = this.el.wrap({
cls: this.innerCls
});
this.wrap = this.innerWrap.wrap({
cls: 'x-form-check-wrap'
});
if(this.boxLabel){
this.labelEl = this.wrap.createChild({
tag: 'label',
htmlFor: this.el.id,
cls: 'x-form-cb-label',
html: this.boxLabel
});
}else{
this.innerWrap.addClass('x-form-check-no-label');
}
},
initValue: function(){
if(this.checked !== undefined){
this.setValue(this.checked);
}else{
if(this.value !== undefined){
this.setValue(this.value);
}
this.checked = this.el.dom.checked;
}
this.originalValue = this.getValue();
},
getRawValue: function(){
return this.rendered ? this.el.dom.checked : this.checked;
},
getValue: function(){
return this.getRawValue() ? this.inputValue : undefined;
},
onClick: function(){
if(Ext.isSafari){
this.focus();
}
if(this.el.dom.checked != this.checked){
this.setValue(this.el.dom.checked);
}
},
setValue: function(v){
this.checked = typeof v == 'boolean' ? v : v == this.inputValue;
if(this.rendered){
this.el.dom.checked = this.checked;
this.el.dom.defaultChecked = this.checked;
this.innerWrap[this.checked ? 'addClass' : 'removeClass']('x-form-check-checked');
this.validate();
}
this.fireEvent('check', this, this.checked);
return this;
},
onMouseEnter: function(){
this.wrap.addClass('x-form-check-over');
},
onMouseLeave: function(){
this.wrap.removeClass('x-form-check-over');
},
onMouseDown: function(){
this.wrap.addClass('x-form-check-down');
},
onMouseUp: function(){
this.wrap.removeClass('x-form-check-down');
},
onFocus: function(){
Ext.form.Checkbox.superclass.onFocus.call(this);
this.wrap.addClass('x-form-check-focus');
},
onBlur: function(){
Ext.form.Checkbox.superclass.onBlur.call(this);
this.wrap.removeClass('x-form-check-focus');
}
});
Ext.override(Ext.form.Radio, {
innerCls: 'x-form-radio-inner',
onClick: Ext.form.Radio.superclass.onClick,
setValue: function(v){
Ext.form.Radio.superclass.setValue.call(this, v);
if(this.rendered && this.checked){
var p = this.el.up('form') || Ext.getBody(),
els = p.select('input[name=' + this.el.dom.name + ']'),
id = this.el.dom.id;
els.each(function(el){
if(el.dom.id != id){
Ext.getCmp(el.dom.id).setValue(false);
}
});
}
return this;
}
});

And include the following stylesheet:
(don't forget to adjust the image paths to your own file locations)

.x-form-check-wrap {
position: relative;
padding: 3px 0;
height: auto;
line-height: 14px;
}
.x-form-check-wrap label.x-form-cb-label {
margin-left: 17px;
padding: 0 3px 0 0;
}
.x-form-checkbox, .x-form-radio {
width: 13px;
height: 13px;
-moz-opacity: 0;
opacity: 0;
-ms-filter: 'alpha(opacity=0)';
filter: alpha(opacity=0);
}
.ext-ie .x-form-check-wrap input {
width: 13px;
height: 13px;
}
.x-form-checkbox-inner, .x-form-radio-inner {
position: absolute;
left: 0px;
top: 4px;
width: 13px;
height: 13px;
}
.x-form-check-no-label {
position: relative;
}
.x-form-checkbox-inner {
background: url('../images/default/form/checkbox.gif') no-repeat 0 0;
}
.x-form-radio-inner {
background: url('../images/default/form/radio.gif') no-repeat 0 0;
}
.x-form-check-focus .x-form-checkbox-inner, .x-form-check-over .x-form-checkbox-inner,
.x-form-check-focus .x-form-radio-inner, .x-form-check-over .x-form-radio-inner {
background-position: -13px 0;
}
.x-form-check-down .x-form-checkbox-inner,
.x-form-check-down .x-form-radio-inner {
background-position: -26px 0;
}
.x-form-check-checked {
background-position: 0 -13px;
}
.x-form-check-focus .x-form-check-checked, .x-form-check-over .x-form-check-checked {
background-position: -13px -13px;
}
.x-form-check-down .x-form-check-checked {
background-position: -26px -13px;
}

If you also want validation support you need the following code:

Ext.override(Ext.form.Field, {
markEl: 'el',
markInvalid: function(msg){
if(!this.rendered || this.preventMark){
return;
}
msg = msg || this.invalidText;
var mt = this.getMessageHandler();
if(mt){
mt.mark(this, msg);
}else if(this.msgTarget){
this[this.markEl].addClass(this.invalidClass);
var t = Ext.getDom(this.msgTarget);
if(t){
t.innerHTML = msg;
t.style.display = this.msgDisplay;
}
}
this.fireEvent('invalid', this, msg);
},
clearInvalid : function(){
if(!this.rendered || this.preventMark){
return;
}
var mt = this.getMessageHandler();
if(mt){
mt.clear(this);
}else if(this.msgTarget){
this[this.markEl].removeClass(this.invalidClass);
var t = Ext.getDom(this.msgTarget);
if(t){
t.innerHTML = '';
t.style.display = 'none';
}
}
this.fireEvent('valid', this);
}
});
Ext.apply(Ext.form.MessageTargets, {
'qtip': {
mark: function(field, msg){
var markEl = field[field.markEl];
markEl.addClass(field.invalidClass);
markEl.dom.qtip = msg;
markEl.dom.qclass = 'x-form-invalid-tip';
if(Ext.QuickTips){
Ext.QuickTips.enable();
}
},
clear: function(field){
var markEl = field[field.markEl];
markEl.removeClass(field.invalidClass);
markEl.dom.qtip = '';
}
},
'title': {
mark: function(field, msg){
var markEl = field[field.markEl];
markEl.addClass(field.invalidClass);
markEl.dom.title = msg;
},
clear: function(field){
field[field.markEl].dom.title = '';
}
},
'under': {
mark: function(field, msg){
var markEl = field[field.markEl], errorEl = field.errorEl;
markEl.addClass(field.invalidClass);
if(!errorEl){
var elp = field.getErrorCt();
if(!elp){
markEl.dom.title = msg;
return;
}
errorEl = field.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
errorEl.setWidth(elp.getWidth(true) - 20);
}
errorEl.update(msg);
Ext.form.Field.msgFx[field.msgFx].show(errorEl, field);
},
clear: function(field){
var markEl = field[field.markEl], errorEl = field.errorEl;
markEl.removeClass(field.invalidClass);
if(errorEl){
Ext.form.Field.msgFx[field.msgFx].hide(errorEl, field);
}else{
markEl.dom.title = '';
}
}
},
'side': {
mark: function(field, msg){
var markEl = field[field.markEl], errorIcon = field.errorIcon;
markEl.addClass(field.invalidClass);
if(!errorIcon){
var elp = field.getErrorCt();
if(!elp){
markEl.dom.title = msg;
return;
}
errorIcon = field.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
}
field.alignErrorIcon();
errorIcon.dom.qtip = msg;
errorIcon.dom.qclass = 'x-form-invalid-tip';
errorIcon.show();
field.on('resize', field.alignErrorIcon, field);
},
clear: function(field){
var markEl = field[field.markEl], errorIcon = field.errorIcon;
markEl.removeClass(field.invalidClass);
if(errorIcon){
errorIcon.dom.qtip = '';
errorIcon.hide();
field.un('resize', field.alignErrorIcon, field);
}else{
markEl.dom.title = '';
}
}
}
});
Ext.override(Ext.form.Checkbox, {
markEl: 'wrap',
mustCheckText: 'This field is required',
alignErrorIcon: function(){
this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
},
markInvalid: Ext.form.Checkbox.superclass.markInvalid,
clearInvalid: Ext.form.Checkbox.superclass.clearInvalid,
validateValue : function(value){
if(this.mustCheck && !value){
this.markInvalid(this.mustCheckText);
return false;
}
if(this.vtype){
var vt = Ext.form.VTypes;
if(!vt[this.vtype](value, this)){
this.markInvalid(this.vtypeText || vt[this.vtype +'Text']);
return false;
}
}
if(typeof this.validator == "function"){
var msg = this.validator(value);
if(msg !== true){
this.markInvalid(msg);
return false;
}
}
if(this.regex && !this.regex.test(value)){
this.markInvalid(this.regexText);
return false;
}
return true;
}
});
Ext.override(Ext.form.Radio, {
markInvalid: Ext.form.Radio.superclass.markInvalid,
clearInvalid: Ext.form.Radio.superclass.clearInvalid
});

Note: I modified Checkbox.getValue and setValue so they more closely mimic traditional checkboxes and radios.
- If you currently load a checkbox with a value 'true' or '1' (string) then you need to set the inputValue to this value, otherwise the checkbox won't be checked.
- getValue returns the current value (inputValue or undefined). If you want the checked state (true or false) then you can use getRawValue.

galdaka
27 Apr 2009, 9:46 AM
Will be added this code to SVN?

Greetings,

VinylFox
27 Apr 2009, 4:48 PM
The consensus is no. Jack said it was to keep the look of a checkbox/radio consistent with what the user is use to in their OS. I think the motivation had more to do with tabbing and focus issues that surfaced with the introduction of the sprite version.

With a little css, its not hard to get the OS's checkboxes to line up with labels properly. I still think the sprite versions are way better looking tho.

PS. Thanks Condor for posting this.

nayato
16 May 2009, 2:24 AM
Good work, Condor!

I would also change onMouseLeave this way:

onMouseLeave: function(){
this.wrap.removeClass('x-form-check-over');
this.wrap.removeClass('x-form-check-down');
},


I also found that top in CSS-class for x-form-checkbox-inner class caused an odd layout so I removed it and now it works like a charm. Is it only me or does anyone else having this problem?

Cheers!

DamianHartin
26 Nov 2009, 12:26 AM
Thanks for this Condor - saved me a lot of heartache in FF

However IE seems to cause it's usual problems and not play nice.

See image for what's happening

http://www.extjs.com/forum/%3Cimg%20src=%22http://img690.imageshack.us/img690/933/ieradiogroup.png%22%20alt=%22Image%20Hosted%20by%20ImageShack.us%22/%3E%3Cbr/%3EBy%20%3Ca%20target=%22_new%22%20href=%22http://profile.imageshack.us/user/dhartin%22%3Edhartin%3C/a%3E%20at%202009-11-26I have the following:

Main aspx page


<%@ Page Language="VB" AutoEventWireup="false" CodeFile="_Scratch.aspx.vb" Inherits="_Scratch" %>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<link href="resources/css/ext-all.css" rel="stylesheet" type="text/css" />
<link href="resources/css/RadioGroup.css" rel="stylesheet" type="text/css" />

<script src="adapter/ext/ext-base.js" type="text/javascript"></script>
<script src="ext-all-debug.js" type="text/javascript"></script>

<script src="resources/js/Application.Overrides.js" type="text/javascript"></script>
<script src="resources/js/_Scratch-RadioButtonTest.js" type="text/javascript"></script>

<title id="PageTitle">Scratch</title></head>
<body>
<form id="form1" runat="server">
<div>

</div>
</form>
</body>
</html>
Test JS:


Ext.BLANK_IMAGE_URL = 'resources/images/default/s.gif';
Ext.ns('Application');


// application main entry point
Ext.onReady(function() {

Ext.QuickTips.init();

var vptMain = new Ext.Viewport({
id:'Scratch_ViewPort'
,layout:'border'
,frame: false
,title:'Scratch Main Header'
,items:[{
region:'north'
,id: 'srcNorthPanel'
,height:10
,border: false
,margins: '0 0 0 0'
,collapsible:false
},{

region:'center'
,id: 'scratchCenterPanel'
,layout:'form'
,margins:'5 5 5 5'
,title:'Scratch Search'
,items:[{
xtype:'fieldset'
,title: 'Radio Groups'
,autoHeight: true
,items: [{
xtype: 'radiogroup',
fieldLabel: 'Multi-Column<br />(horiz. auto-width)',
columns: 3,
items: [
{boxLabel: 'Item 1', name: 'rb-horiz', inputValue: 1},
{boxLabel: 'Item 2', name: 'rb-horiz', inputValue: 2, checked: true},
{boxLabel: 'Item 3', name: 'rb-horiz', inputValue: 3},
{boxLabel: 'Item 4', name: 'rb-horiz', inputValue: 4},
{boxLabel: 'Item 5', name: 'rb-horiz', inputValue: 5}
]
}]
}]

}]
})

}); // eo function onReady
Used in conjunction with the overides provide on this thread

Any idea's as to what the problem may be?

Thanks,
Damian

sumit.madan
10 Dec 2009, 11:11 AM
If you're using ExtJS 3.0.3, the CSS for the <INPUT> element for checkboxes and radios was changed. This results in the sprites being shown in IE, when using the themed checkboxes and radios

For checkboxes apply the following fixes:

> In ext-all.css comment out the following CSS rule (line 915):
.x-form-check-wrap input{
vertical-align: bottom;
}> When creating a checkbox in a form, assign width : 13 in the config. ExtJS 3.0.3 has started assigning the config width to the input element, which results in the sprites being shown.

I believe the fixes for radios would also be similiar. I'm not using them atm.

dawesi
14 Dec 2009, 6:34 PM
nice one - thanks again.

tonedeaf
20 Dec 2009, 12:22 PM
For ExtJS 3.1 additional CSS rules for displaying themed checkboxes, in addition to Condor's first post:



.ext-ie6 .x-form-check-wrap input, .ext-border-box .x-form-check-wrap input {
margin-top: 0px;
}

.x-form-check-wrap input {
vertical-align: baseline;
}

Also, all ExtJS checkboxes have to be created with a width : 13 in their config parameters, for the sprites to show correctly.

Dumbledore
8 Apr 2010, 9:32 PM
For Ext 3.2 i must replace setValue for have the same functions as the original function. Without that the handler is not called:


setValue : function(v){
var checked = this.checked ;
this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
if(this.rendered){
this.el.dom.checked = this.checked;
this.el.dom.defaultChecked = this.checked;
this.innerWrap[this.checked ? 'addClass' : 'removeClass']('x-form-check-checked');
this.validate();
}
if(checked != this.checked){
this.fireEvent('check', this, this.checked);
if(this.handler){
this.handler.call(this.scope || this, this, this.checked);
}
}
return this;
},

Dumbledore
13 May 2010, 11:20 PM
when using this override there is a design glitch if a checkbox is inside a toolbar (see picture). And the checkbox can not direct clicked.

Any idea? (The border is from firebug)

Dumbledore
14 May 2010, 11:59 PM
found it. For Ext 3.2. the bold lines in onRender must be added:


onRender: function(ct, position){
Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
if(this.inputValue !== undefined){
this.el.dom.value = this.inputValue;
}else{
this.inputValue = this.el.dom.value;
}
this.innerWrap = this.el.wrap({
cls: this.innerCls
});
this.wrap = this.innerWrap.wrap({
cls: 'x-form-check-wrap'
});
if(this.boxLabel){
this.labelEl = this.wrap.createChild({
tag: 'label',
htmlFor: this.el.id,
cls: 'x-form-cb-label',
html: this.boxLabel
});
}else{
this.innerWrap.addClass('x-form-check-no-label');
}

// Need to repaint for IE, otherwise positioning is broken
if(Ext.isIE){
this.wrap.repaint();
}

this.resizeEl = this.positionEl = this.wrap;
},

WixSL
11 Jun 2010, 6:12 AM
Could anybody find a real solution for themeing checkboxes?
I tried all of this and the checkboxes don't even appear (FF 3.6.3 / Opera 10.53 - Windows 2000).
I'm useing Ext v3.2.1

arian10daddy
14 Jun 2010, 4:07 AM
Hi All,

I am not able to get the radiogroup working after my upgrade from Ext 2.2 to Ext 3.2.1...
Also, everything inside my viewport is coming to the top left portion of the screen. I've tried the above options but that doesn't work probably because it refers to radio and not radiogroup. Could you folks please help me in this regard?

Varun

Dumbledore
4 Nov 2010, 1:55 AM
It seems there is a problem when using this with ext3.3. The overwrite will return value/undefined by getValue() the checkbox in ext will return true/false.
This is realy a problem when using Ext.form.BasicForm.updateRecord()!

A solution is not to overwrite the getValue()... or is there a better way?

Condor
4 Nov 2010, 2:08 AM
You could specify inputValue:true or you could make getValue return getRawValue directly.

Dumbledore
4 Nov 2010, 2:34 AM
i try inputVlaue: true, but when the checkbox is not checked the value is undefined which won´t work with Ext.form.BasicForm.updateRecord().

Perhaps so:


getValue: function(){
return this.getRawValue() ? this.inputValue : this.getRawValue();
},

Condor
4 Nov 2010, 2:54 AM
If you really just want a true/false value from getValue then you only need:

getValue: function(){
return this.getRawValue();
},

cyberal
20 Apr 2011, 7:20 AM
Hi,

Thanks Condor for this good works :).

In my project for checkbox I need to change the image of the checkbox, to do this I have changed the code :


Ext.override(Ext.form.Checkbox, {
checked: undefined,
actionMode: 'wrap',

innerCls: 'x-form-checkbox-inner',
overCls: 'x-form-check-over',
downCls: 'x-form-check-down',
focusCls: 'x-form-check-focus',
checkedCls: 'x-form-check-checked',

onResize: function(){
Ext.form.Checkbox.superclass.onResize.apply(this, arguments);
if(!this.boxLabel){
this.innerWrap.alignTo(this.wrap, 'c-c');
}
},
initEvents: function(){
Ext.form.Checkbox.superclass.initEvents.call(this);
this.mon(this.el, {
click: this.onClick,
change: this.onClick,
mouseenter: this.onMouseEnter,
mouseleave: this.onMouseLeave,
mousedown: this.onMouseDown,
mouseup: this.onMouseUp,
scope: this
});
},
onRender: function(ct, position){
Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
if(this.inputValue !== undefined){
this.el.dom.value = this.inputValue;
}else{
this.inputValue = this.el.dom.value;
}
this.innerWrap = this.el.wrap({
cls: this.innerCls
});
this.wrap = this.innerWrap.wrap({
cls: 'x-form-check-wrap'
});
if(this.boxLabel){
this.labelEl = this.wrap.createChild({
tag: 'label',
htmlFor: this.el.id,
cls: 'x-form-cb-label',
html: this.boxLabel
});
}else {
this.innerWrap.addClass('x-form-check-no-label');
}

// Need to repaint for IE, otherwise positioning is broken
if(Ext.isIE){
this.wrap.repaint();
}

this.resizeEl = this.positionEl = this.wrap;
},
initValue: function(){
if(this.checked !== undefined){
this.setValue(this.checked);
}else{
if(this.value !== undefined){
this.setValue(this.value);
}
this.checked = this.el.dom.checked;
}
this.originalValue = this.getValue();
},
getRawValue: function(){
return this.rendered ? this.el.dom.checked : this.checked;
},
getValue: function(){
return this.getRawValue() ? this.inputValue : undefined;
},
onClick: function(){
if(Ext.isSafari){
this.focus();
}
if(this.el.dom.checked != this.checked){
this.setValue(this.el.dom.checked);
}
},
setValue: function(v){
this.checked = typeof v == 'boolean' ? v : v == this.inputValue;
if(this.rendered){
this.el.dom.checked = this.checked;
this.el.dom.defaultChecked = this.checked;
this.innerWrap[this.checked ? 'addClass' : 'removeClass'](this.checkedCls);
this.validate();
}
this.fireEvent('check', this, this.checked);
if(this.handler){
this.handler.call(this.scope || this, this, this.checked);
}
return this;
},
onMouseEnter: function(){
this.wrap.addClass(this.overCls);
},
onMouseLeave: function(){
this.wrap.removeClass(this.overCls);
},
onMouseDown: function(){
this.wrap.addClass(this.downCls);
},
onMouseUp: function(){
this.wrap.removeClass(this.downCls);
},
onFocus: function(){
Ext.form.Checkbox.superclass.onFocus.call(this);
this.wrap.addClass(this.focusCls);
},
onBlur: function(){
Ext.form.Checkbox.superclass.onBlur.call(this);
this.wrap.removeClass(this.focusCls);
}
});
Ext.override(Ext.form.Radio, {
innerCls: 'x-form-radio-inner',
onClick: Ext.form.Radio.superclass.onClick,
setValue: function(v){
Ext.form.Radio.superclass.setValue.call(this, v);
if(this.rendered && this.checked){
var p = this.el.up('form') || Ext.getBody(),
els = p.select('input[name=' + this.el.dom.name + ']'),
id = this.el.dom.id;
els.each(function(el){
if(el.dom.id != id){
Ext.getCmp(el.dom.id).setValue(false);
}
});
}
return this;
}
});


I think it is a good idea to allow the user to change the CSS classes.