Well, it's certainly not easy to make yourself. Not easy, but quite fun. This is my first attempt at creating a plugin so please excuse the poor code quality. The code uses (but is not an extension of) jQuery UI. Specifically, it uses the draggable component for the handle, and some UI CSS classes.
This is certainly a work in progress. It is by no means finished. These are the things I'd like to see done before declaring this thing done:
- Keyboard control for moving the handle, for accessibility reasons
- Implement WAI-RIA standards, again for accessibility
- Set up more options, maybe even events
- Refactor the code into something a little more manageable.
The first two are very important, and is the reason why this code shouldn't be used yet. You are, however, welcome to hack around the code and play around with it. All suggestions on how this thing might be made to work better are welcome.
Live demo: http://jsfiddle.net/RDkBL/7/
(function($) {
$.fn.slideButton = function(options) {
// Settings
var settings = $.extend({
slideSpeed: 10,
revertSpeed: 5,
labelWidth: 0
}, options);
this.each(function() {
var container = $(this);
var label = container.children('label');
var input = container.children(':radio');
var maxWidth = 0;
if (label.length != 2 || input.length != 2) {
throw new Error("The container must contain two radio buttons and two labels");
}
// move() does the animation associated with
// state changing for the button
function move(direction, speed) {
var amount = direction === 'right' ? halfWidth : -1;
var duration = (direction === 'right' ? halfWidth - handle.position().left : handle.position().left) * speed;
handle.animate({
left: amount
}, duration);
input.eq(direction === 'right' ? 0 : 1).attr('checked', true);
}
// Handles changing by checking current state
function updateState() {
move(handle.hasClass('on') ? 'right' : 'left', settings.slideSpeed);
handle.toggleClass('on');
return false;
}
// Reverts position - think of this as
// the opposite of updateState()
function revert() {
move(handle.hasClass('on') ? 'left' : 'right', settings.revertSpeed);
return false;
}
// Adding classes and hiding input elements
container.addClass('ui-sbutton-container ui-corner-all');
input.addClass('ui-helper-hidden-accessible');
label.addClass('ui-sbutton-label');
// Setting label widths - if none set,
// then loop through all of them and use the biggest
if (settings.labelWidth) {
maxWidth = settings.labelWidth;
} else {
label.each(function() {
var w = $(this).outerWidth();
if (w > maxWidth) {
maxWidth = w;
}
});
}
// Padding was useful for finding largest width,
// but will now interfere when setting explicit widths
label.width(maxWidth).css({
'padding-left': 0,
'padding-right': 0
});
// Getting all important half width for later use
var halfWidth = (container.outerWidth() / 2);
// Very messy chain that does element creation,
// insertion and event handling all at once
var handle = $('<a />')
.addClass('ui-sbutton-handle ui-corner-all').hover(function() {
$(this).toggleClass('ui-sbutton-active');
}).dblclick(function(){
updateState();
return false;
}).appendTo(container).width(maxWidth - 1).draggable({
containment: 'parent',
axis: 'x',
stop: function(event, ui) {
var left = $(this).position().left;
if ((left > (halfWidth - 1) / 2 && handle.hasClass('on')) || (left < (halfWidth / 2 - 1) && !handle.hasClass('on'))) {
updateState();
} else {
revert();
}
}
});
// Set up initial state of the button
if (input.first().is(':checked')) {
move('right', 0);
} else {
handle.addClass('on');
}
});
return this;
};})(jQuery);