(function($, switchbutton){ $.widget('switchbutton.switchbutton', { options: { classes: '', duration: 200, dragThreshold: 5, autoResize: true, labels: true, checkedLabel: 'ON', uncheckedLabel: 'OFF', disabledClass: 'ui-switchbutton-disabled ui-state-disabled', template: '
' + '' + '' + '
' + '
' }, _create: function() { if(!this.element.is(':checkbox')) { return; } this._wrapCheckboxInContainer(); this._attachEvents(); this._globalEvents(); this._disableTextSelection(); if(this.element.prop('checked')) { this.$container.toggleClass('ui-state-active', this.element.prop('checked')); } if(this.options.autoResize) { this._autoResize(); } this._initialPosition(); }, _wrapCheckboxInContainer: function() { this.$container = $.tmpl(this.options.template, this.options); this.element.after(this.$container); this.element.remove(); this.$container.append(this.element); this.$disabledLabel = this.$container.children('.ui-switchbutton-disabled'); this.$disabledSpan = this.$disabledLabel.children('span'); this.$enabledLabel = this.$container.children('.ui-switchbutton-enabled'); this.$enabledSpan = this.$enabledLabel.children('span'); this.$handle = this.$container.children('.ui-switchbutton-handle'); }, _attachEvents: function() { var obj = this; this.$container // Listen for keyboard events such as , and .bind('keydown', function(event){ //Ignore if a key combo was used if(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey){ return; } //Catch , , and if(event.keyCode == 32 || event.keyCode == 37 || event.keyCode == 39) event.preventDefault(); //Ignore if the element is disabled if(obj.element.prop('disabled')) { return; } //Determine if we're supposed to toggle var checked = obj.element.prop('checked') if(event.keyCode == 32 || (event.keyCode == 37 && checked) || (event.keyCode == 39 && !checked)) { //Perform the toggle var willChangeEvent = jQuery.Event('willChange'); obj.element.trigger(willChangeEvent); if(willChangeEvent.isDefaultPrevented()) return; checked = !checked; obj.element.prop('checked', checked); obj.$container.toggleClass('ui-state-active', checked); obj.element.change(); obj.element.trigger('didChange'); } }) // A mousedown anywhere in the control will start tracking for dragging .bind('mousedown touchstart', function(event) { event.preventDefault(); if(obj.element.prop('disabled')) { return; } $(this).focus(); var x = event.pageX || event.originalEvent.changedTouches[0].pageX; $[switchbutton].currentlyClicking = obj.$handle; $[switchbutton].dragStartPosition = x; $[switchbutton].handleLeftOffset = parseInt(obj.$handle.css('left'), 10) || 0; $[switchbutton].dragStartedOn = obj.element; }) // Utilize event bubbling to handle drag on any element beneath the container .bind('iPhoneDrag', function(event, x) { event.preventDefault(); if(obj.element.prop('disabled')) { return; } if(obj.element != $[switchbutton].dragStartedOn) { return; } var p = (x + $[switchbutton].handleLeftOffset - $[switchbutton].dragStartPosition) / obj.rightSide; if(p < 0) { p = 0; } if(p > 1) { p = 1; } obj.$handle.css({ 'left': p * obj.rightSide }); obj.$enabledLabel.css({ 'width': p * obj.rightSide }); obj.$disabledSpan.css({ 'margin-right': -p * obj.rightSide }); obj.$enabledSpan.css({ 'margin-left': -(1 - p) * obj.rightSide }); }) // Utilize event bubbling to handle drag end on any element beneath the container .bind('iPhoneDragEnd', function(event, x) { if(obj.element.prop('disabled')) { return; } var willChangeEvent = jQuery.Event('willChange'); obj.element.trigger(willChangeEvent); if(willChangeEvent.isDefaultPrevented()) { checked = obj.element.prop('checked'); } else { var checked; if($[switchbutton].dragging) { var p = (x - $[switchbutton].dragStartPosition) / obj.rightSide; checked = (p < 0) ? Math.abs(p) < 0.5 : p >= 0.5; } else { checked = !obj.element.prop('checked'); } } $[switchbutton].currentlyClicking = null; $[switchbutton].dragging = null; obj.element.prop('checked', checked); obj.$container.toggleClass('ui-state-active', checked); obj.element.change(); obj.element.trigger('didChange'); }); // Animate when we get a change event this.element.change(function() { obj.refresh(); var new_left = obj.element.prop('checked') ? obj.rightSide : 0; obj.$handle.animate({ 'left': new_left }, obj.options.duration); obj.$enabledLabel.animate({ 'width': new_left }, obj.options.duration); obj.$disabledSpan.animate({ 'margin-right': -new_left }, obj.options.duration); obj.$enabledSpan.animate({ 'margin-left': new_left - obj.rightSide }, obj.options.duration); }); }, _globalEvents: function() { if($[switchbutton].initComplete) { return; } var opt = this.options; $(document) // As the mouse moves on the page, animate if we are in a drag state .bind('mousemove touchmove', function(event) { if(!$[switchbutton].currentlyClicking) { return; } event.preventDefault(); var x = event.pageX || event.originalEvent.changedTouches[0].pageX; if(!$[switchbutton].dragging && (Math.abs($[switchbutton].dragStartPosition - x) > opt.dragThreshold)) { $[switchbutton].dragging = true; } $(event.target).trigger('iPhoneDrag', [x]); }) // When the mouse comes up, leave drag state .bind('mouseup touchend', function(event) { if(!$[switchbutton].currentlyClicking) { return; } event.preventDefault(); var x = event.pageX || event.originalEvent.changedTouches[0].pageX; $($[switchbutton].currentlyClicking).trigger('iPhoneDragEnd', [x]); }); }, _disableTextSelection: function() { // Elements containing text should be unselectable $([this.$handle, this.$disabledLabel, this.$enabledLabel, this.$container]).attr('unselectable', 'on'); }, _autoResize: function() { var onLabelWidth = this.$enabledLabel.width(), offLabelWidth = this.$disabledLabel.width(), spanPadding = this.$disabledSpan.innerWidth() - this.$disabledSpan.width() handleMargins = this.$handle.outerWidth() - this.$handle.innerWidth(); var containerWidth = handleWidth = (onLabelWidth > offLabelWidth) ? onLabelWidth : offLabelWidth; this.$handle.css({ 'width': handleWidth }); handleWidth = this.$handle.width(); containerWidth += handleWidth + 6; spanWidth = containerWidth - handleWidth - spanPadding - handleMargins; this.$container.css({ 'width': containerWidth }); this.$container.find('span').width(spanWidth); }, _initialPosition: function() { this.$disabledLabel.css({ width: this.$container.width() - 5 }); this.rightSide = this.$container.width() - this.$handle.outerWidth(); if(this.element.prop('checked')) { this.$handle.css({ 'left': this.rightSide }); this.$enabledLabel.css({ 'width': this.rightSide }); this.$disabledSpan.css({ 'margin-right': -this.rightSide }); } else { this.$enabledLabel.css({ 'width': 0 }); this.$enabledSpan.css({ 'margin-left': -this.rightSide }); } this.refresh(); }, enable: function() { this.element.prop('disabled', false); this.refresh(); return this._setOption('disabled', false); }, disable: function() { this.element.prop('disabled', true); this.refresh(); return this._setOption('disabled', true); }, widget: function() { return this.$container; }, refresh: function() { if(this.element.prop('disabled')) { this.$container.addClass(this.options.disabledClass); return false; } else { this.$container.removeClass(this.options.disabledClass); } } }); })(jQuery, 'switchbutton');