PDA

View Full Version : A simple 2-state knob (aka iPhone-like toggle)



Gourmet
25 May 2011, 3:13 AM
Hi everyone,

Sorry to say that but I consider the well-behaving On/Off toggle (http://www.sencha.com/forum/showthread.php?109705-On-Off-toggle-buttom) that was proposed in 2010 as missing something: it asked for images.
Images are not customizable whereas CSS (and CSS3) are fully customizable.

That's why I'm proposing the following component that aims at mimic the iPhone's toggle.
Of course, it's not perfect.
For example, on an iPhone's real toggle all the toggle is moving not only the thumb: a matter of CSS.
This behaviour is not difficult to implement. I'll propose it later.
For example, the component cannot be put anywhere in a container, it's stuck on the left : another matter of CSS.
Another example is that we can dissociate the part that deals with group behaviour in the component's code.

Another component in the tube is a multistate knob (the toggle can be seen as a 2-state knob).
Your comments are welcome.
And stay tune.

The component, Ext.ux.Knob2s whose code is taken from Checkbox.



/*
* A 2-state knob
* A simple code proposed by db
* V. 1.00, 2011-05-24
*/
Ext.ux.Knob2s = Ext.extend(Ext.form.Field, {

componentCls : 'x-knob2s',

//baseCls: 'x-form-knob2s',

text: ['on', 'off'],

status: false,

constructor: function(config) {
this.addEvents(

'on',

'off',

'change'
);

Ext.ux.Knob2s.superclass.constructor.call(this, config);
},

// @private
initComponent: function() {

Ext.ux.Knob2s.superclass.initComponent.call(this);
},

renderTpl: [
'<tpl if="label"><div class="x-form-label"><span>{label}</span></div></tpl>',
'<tpl if="fieldEl">',
'<div class="{componentCls}">',
'<div class="{componentCls}-block {fieldCls}">',
'<div class="{componentCls}-status1"><span class="{componentCls}-text">{text1}</span></div>',
'<div class="{componentCls}-status2"><span class="{componentCls}-text">{text2}</span></div>',
'<div id="{fieldId}-thumb" class="{componentCls}-thumb">&nbsp;</div>',
'<div class="{componentCls}-shadow">&nbsp;</div>',
'</div>',
'</div>',
'</tpl>'
],

onRender: function() {

Ext.apply(this.renderData, {
text1 : String(this.text[0]),
text2 : String(this.text[1]),
fieldId : this.getId()
});

Ext.ux.Knob2s.superclass.onRender.apply(this, arguments);


if (this.fieldEl) {
this.mon(this.fieldEl, {
click: this.onChange,
scope: this
});

this.setStatus(this.getStatus());
this.originalState = this.getStatus();
}
},

onChange: function(e) {
if (e) {
if (e.browserEvent) {
e = e.browserEvent;
}

if (Ext.supports.Touch && !e.isSimulated) {
e.preventDefault();
e.stopPropagation();
return;
}
}

if (this.isOn()) {
this.setStatus(false);
this.fireEvent('off', this);
} else {
this.setStatus(true);
this.fireEvent('on', this);
}
this.fireEvent('change', this, this.status, this.text[(this.status)?0:1]);
},

getStatus: function() {
return this.status;
},

setStatus: function(myNewStatus) {
var newState = this.getBooleanIsOn(myNewStatus),
rendered = this.rendered,
currentState,
field;

this.status = myNewStatus;
if (rendered) {
field = this.fieldEl.dom;
currentState = field.myStatus;
} else {
currentState = !!this.myStatus;
}

if (rendered) {
this.myThumb = Ext.get(this.getId() + '-thumb');
if (this.status) {
this.myThumb.removeCls(this.componentCls + '-thumb');
this.myThumb.addCls(this.componentCls + '-thumb-on');
} else {
this.myThumb.removeCls(this.componentCls + '-thumb-on');
this.myThumb.addCls(this.componentCls + '-thumb');
}
}

if (currentState != newState) {
if (rendered) {
field.myStatus = newState;
} else {
this.myStatus = newState;
}
//this.onChange();
}
return this;
},

//@private

isOn: function() {
return (this.status)
},

getBooleanIsOn: function(value) {
return /^(true|1|on)/i.test(String(value));
},

getSameGroupFields: function() {
var parent = this.el.up('form'),
formComponent = Ext.getCmp(parent.id),
fields = [];

if (formComponent) {
fields = formComponent.getFields(this.getName());
}

return fields;
},


getGroupValues: function() {
var values = [];

this.getSameGroupFields().forEach(function(field) {
if (field.isChecked()) {
values.push(field.getValue());
}
});

return values;
},


setGroupValues: function(values) {
this.getSameGroupFields().forEach(function(field) {
field.setChecked((values.indexOf(field.getValue()) !== -1));
});

return this;
}


});

Ext.reg('knob2sfield', Ext.ux.Knob2s);


The associated CSS:



/*
* CSS for a 2-state knob
* A simple code proposed by db
* V. 1.00, 2011-05-24
*/

.x-field .x-knob2s {
}

.x-knob2s {
}

.x-knob2s-block {
width: 120px;
height: 25px;
top: 10px;
position: relative;
}

.x-knob2s-shadow {
position: absolute;
width: 100%;
height: 100%;
-webkit-border-radius: 4px;
#-webkit-box-sizing: content-box;
-webkit-box-shadow: 4px 4px 7px 1px rgba(0,0,0,0.2) inset;
border: 1px solid #888;
background: transparent;
display: block;
}

.x-knob2s-status1 {
position: absolute;
width: 60px;
height: 25px;
left: 0px;
-webkit-border-top-left-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
background: lightgreen;
text-align: center;
}

.x-knob2s-status2 {
position: absolute;
width: 60px;
height: 25px;
left: 60px;
-webkit-border-top-right-radius: 4px;
-webkit-border-bottom-right-radius: 4px;
background: lightblue;
text-align: center;
}

.x-knob2s-text {
padding: 0px;
margin: 0px;
height:25px;
text-transform: uppercase;
font-family: Arial, Helvetica;
font-size: 16px;
text-overflow: ellipsis;
overflow: hidden;
color: white;
font-weight: bold;
display: block;
}

.x-knob2s-thumb {
position: absolute;
width: 59px;
height:23px;
left: 1px;
top: 1px;
-webkit-border-radius: 4px;
-webkit-box-shadow: rgba(0,0,0,.3) 1px 1px 3px;
-webkit-transition: -webkit-transform .5s ease-in ;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#8e8e8e), to(#afafaf));
display:fixed;
z-index: 2;
}

.x-knob2s-thumb-on {
position: absolute;
width: 59px;
height:23px;
left: 1px;
top: 1px;
-webkit-border-radius: 4px;
-webkit-box-shadow: rgba(0,0,0,.3) 1px 1px 3px;
-webkit-transform: translate(59px);
-webkit-transition: -webkit-transform .5s ease-in ;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#8e8e8e), to(#afafaf));
display:fixed;
z-index: 2;
}



And an example of implementation:



Ext.setup({
glossOnIcon: true,
statusBarStyle: 'black-translucent',
onReady: function() {

var panel = new Ext.Panel({
id: 'panel',
fullscreen: true,
layout: {
type: 'vbox',
align: 'left'
},
items: [{
xtype: 'checkboxfield',
label: 'tata',
},{
xtype: 'knob2sfield',
label: 'toto',
text: ['on', 'off'],
listeners: {
'on': function(obj) {
console.log(' is SET to ON');
},
'off': function(obj) {
console.log(' is SET to OFF');
},
'change': function(obj, newStatus, newValue) {
console.log(' is changed to ' + newValue);
}
}
}]
});
}
});
Regards,

db