jquery.switchbutton.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. /*
  2. * jQuery switchbutton
  3. *
  4. * Based on work by tdreyno on iphone-style-checkboxes for events management
  5. * (https://github.com/tdreyno/iphone-style-checkboxes)
  6. *
  7. * Copyright 2011, L.STEVENIN
  8. * Released under the MIT license.
  9. *
  10. * Depends:
  11. * jquery.ui.widget.js (jQuery UI Widget Factory - http://wiki.jqueryui.com/w/page/12138135/Widget%20factory)
  12. * jquery.tmpl.js (jQuery Templates - http://api.jquery.com/category/plugins/templates/)
  13. */
  14. (function($, switchbutton){
  15. $.widget('switchbutton.switchbutton', {
  16. options: {
  17. classes: '',
  18. duration: 200,
  19. dragThreshold: 5,
  20. autoResize: true,
  21. labels: true,
  22. checkedLabel: 'ON',
  23. uncheckedLabel: 'OFF',
  24. disabledClass: 'ui-switchbutton-disabled ui-state-disabled',
  25. template: '<div class="ui-switchbutton ui-switchbutton-default ${classes} {{if !labels}}ui-switchbutton-no-labels{{/if}}" tabindex=0>' +
  26. '<label class="ui-switchbutton-disabled">' +
  27. '<span>{{if labels}}${uncheckedLabel}{{/if}}</span>' +
  28. '</label>' +
  29. '<label class="ui-switchbutton-enabled">' +
  30. '<span>{{if labels}}${checkedLabel}{{/if}}</span>' +
  31. '</label>' +
  32. '<div class="ui-switchbutton-handle"></div>' +
  33. '</div>'
  34. },
  35. _create: function() {
  36. if(!this.element.is(':checkbox')) {
  37. return;
  38. }
  39. this._wrapCheckboxInContainer();
  40. this._attachEvents();
  41. this._globalEvents();
  42. this._disableTextSelection();
  43. if(this.element.prop('checked')) {
  44. this.$container.toggleClass('ui-state-active', this.element.prop('checked'));
  45. }
  46. if(this.options.autoResize) {
  47. this._autoResize();
  48. }
  49. this._initialPosition();
  50. },
  51. _wrapCheckboxInContainer: function() {
  52. this.$container = $.tmpl(this.options.template, this.options);
  53. this.element.after(this.$container);
  54. this.element.remove();
  55. this.$container.append(this.element);
  56. this.$disabledLabel = this.$container.children('.ui-switchbutton-disabled');
  57. this.$disabledSpan = this.$disabledLabel.children('span');
  58. this.$enabledLabel = this.$container.children('.ui-switchbutton-enabled');
  59. this.$enabledSpan = this.$enabledLabel.children('span');
  60. this.$handle = this.$container.children('.ui-switchbutton-handle');
  61. },
  62. _attachEvents: function() {
  63. var obj = this;
  64. this.$container
  65. // Listen for keyboard events such as <space>, <left> and <right>
  66. .bind('keydown', function(event){
  67. //Ignore if a key combo was used
  68. if(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey){
  69. return;
  70. }
  71. //Catch <space>, <left>, and <right>
  72. if(event.keyCode == 32 || event.keyCode == 37 || event.keyCode == 39) event.preventDefault();
  73. //Ignore if the element is disabled
  74. if(obj.element.prop('disabled')) {
  75. return;
  76. }
  77. //Determine if we're supposed to toggle
  78. var checked = obj.element.prop('checked')
  79. if(event.keyCode == 32 || (event.keyCode == 37 && checked) || (event.keyCode == 39 && !checked)) {
  80. //Perform the toggle
  81. var willChangeEvent = jQuery.Event('willChange');
  82. obj.element.trigger(willChangeEvent);
  83. if(willChangeEvent.isDefaultPrevented()) return;
  84. checked = !checked;
  85. obj.element.prop('checked', checked);
  86. obj.$container.toggleClass('ui-state-active', checked);
  87. obj.element.change();
  88. obj.element.trigger('didChange');
  89. }
  90. })
  91. // A mousedown anywhere in the control will start tracking for dragging
  92. .bind('mousedown touchstart', function(event) {
  93. event.preventDefault();
  94. if(obj.element.prop('disabled')) { return; }
  95. $(this).focus();
  96. var x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  97. $[switchbutton].currentlyClicking = obj.$handle;
  98. $[switchbutton].dragStartPosition = x;
  99. $[switchbutton].handleLeftOffset = parseInt(obj.$handle.css('left'), 10) || 0;
  100. $[switchbutton].dragStartedOn = obj.element;
  101. })
  102. // Utilize event bubbling to handle drag on any element beneath the container
  103. .bind('iPhoneDrag', function(event, x) {
  104. event.preventDefault();
  105. if(obj.element.prop('disabled')) { return; }
  106. if(obj.element != $[switchbutton].dragStartedOn) { return; }
  107. var p = (x + $[switchbutton].handleLeftOffset - $[switchbutton].dragStartPosition) / obj.rightSide;
  108. if(p < 0) { p = 0; }
  109. if(p > 1) { p = 1; }
  110. obj.$handle.css({ 'left': p * obj.rightSide });
  111. obj.$enabledLabel.css({ 'width': p * obj.rightSide });
  112. obj.$disabledSpan.css({ 'margin-right': -p * obj.rightSide });
  113. obj.$enabledSpan.css({ 'margin-left': -(1 - p) * obj.rightSide });
  114. })
  115. // Utilize event bubbling to handle drag end on any element beneath the container
  116. .bind('iPhoneDragEnd', function(event, x) {
  117. if(obj.element.prop('disabled')) { return; }
  118. var willChangeEvent = jQuery.Event('willChange');
  119. obj.element.trigger(willChangeEvent);
  120. if(willChangeEvent.isDefaultPrevented()) {
  121. checked = obj.element.prop('checked');
  122. }
  123. else {
  124. var checked;
  125. if($[switchbutton].dragging) {
  126. var p = (x - $[switchbutton].dragStartPosition) / obj.rightSide;
  127. checked = (p < 0) ? Math.abs(p) < 0.5 : p >= 0.5;
  128. }
  129. else {
  130. checked = !obj.element.prop('checked');
  131. }
  132. }
  133. $[switchbutton].currentlyClicking = null;
  134. $[switchbutton].dragging = null;
  135. obj.element.prop('checked', checked);
  136. obj.$container.toggleClass('ui-state-active', checked);
  137. obj.element.change();
  138. obj.element.trigger('didChange');
  139. });
  140. // Animate when we get a change event
  141. this.element.change(function() {
  142. obj.refresh();
  143. var new_left = obj.element.prop('checked') ? obj.rightSide : 0;
  144. obj.$handle.animate({ 'left': new_left }, obj.options.duration);
  145. obj.$enabledLabel.animate({ 'width': new_left }, obj.options.duration);
  146. obj.$disabledSpan.animate({ 'margin-right': -new_left }, obj.options.duration);
  147. obj.$enabledSpan.animate({ 'margin-left': new_left - obj.rightSide }, obj.options.duration);
  148. });
  149. },
  150. _globalEvents: function() {
  151. if($[switchbutton].initComplete) {
  152. return;
  153. }
  154. var opt = this.options;
  155. $(document)
  156. // As the mouse moves on the page, animate if we are in a drag state
  157. .bind('mousemove touchmove', function(event) {
  158. if(!$[switchbutton].currentlyClicking) { return; }
  159. event.preventDefault();
  160. var x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  161. if(!$[switchbutton].dragging && (Math.abs($[switchbutton].dragStartPosition - x) > opt.dragThreshold)) {
  162. $[switchbutton].dragging = true;
  163. }
  164. $(event.target).trigger('iPhoneDrag', [x]);
  165. })
  166. // When the mouse comes up, leave drag state
  167. .bind('mouseup touchend', function(event) {
  168. if(!$[switchbutton].currentlyClicking) { return; }
  169. event.preventDefault();
  170. var x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  171. $($[switchbutton].currentlyClicking).trigger('iPhoneDragEnd', [x]);
  172. });
  173. },
  174. _disableTextSelection: function() {
  175. // Elements containing text should be unselectable
  176. $([this.$handle, this.$disabledLabel, this.$enabledLabel, this.$container]).attr('unselectable', 'on');
  177. },
  178. _autoResize: function() {
  179. var onLabelWidth = this.$enabledLabel.width(),
  180. offLabelWidth = this.$disabledLabel.width(),
  181. spanPadding = this.$disabledSpan.innerWidth() - this.$disabledSpan.width()
  182. handleMargins = this.$handle.outerWidth() - this.$handle.innerWidth();
  183. var containerWidth = handleWidth = (onLabelWidth > offLabelWidth) ? onLabelWidth : offLabelWidth;
  184. this.$handle.css({ 'width': handleWidth });
  185. handleWidth = this.$handle.width();
  186. containerWidth += handleWidth + 6;
  187. spanWidth = containerWidth - handleWidth - spanPadding - handleMargins;
  188. this.$container.css({ 'width': containerWidth });
  189. this.$container.find('span').width(spanWidth);
  190. },
  191. _initialPosition: function() {
  192. this.$disabledLabel.css({ width: this.$container.width() - 5 });
  193. this.rightSide = this.$container.width() - this.$handle.outerWidth();
  194. if(this.element.prop('checked')) {
  195. this.$handle.css({ 'left': this.rightSide });
  196. this.$enabledLabel.css({ 'width': this.rightSide });
  197. this.$disabledSpan.css({ 'margin-right': -this.rightSide });
  198. }
  199. else {
  200. this.$enabledLabel.css({ 'width': 0 });
  201. this.$enabledSpan.css({ 'margin-left': -this.rightSide });
  202. }
  203. this.refresh();
  204. },
  205. enable: function() {
  206. this.element.prop('disabled', false);
  207. this.refresh();
  208. return this._setOption('disabled', false);
  209. },
  210. disable: function() {
  211. this.element.prop('disabled', true);
  212. this.refresh();
  213. return this._setOption('disabled', true);
  214. },
  215. widget: function() {
  216. return this.$container;
  217. },
  218. refresh: function() {
  219. if(this.element.prop('disabled')) {
  220. this.$container.addClass(this.options.disabledClass);
  221. return false;
  222. }
  223. else {
  224. this.$container.removeClass(this.options.disabledClass);
  225. }
  226. }
  227. });
  228. })(jQuery, 'switchbutton');