iphone-style-checkboxes.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*!
  2. // iPhone-style Checkboxes jQuery plugin
  3. // Copyright Thomas Reynolds, licensed GPL & MIT
  4. */
  5. ;(function($, iphoneStyle) {
  6. // Constructor
  7. $[iphoneStyle] = function(elem, options) {
  8. this.$elem = $(elem);
  9. // Import options into instance variables
  10. var obj = this;
  11. $.each(options, function(key, value) {
  12. obj[key] = value;
  13. });
  14. // Initialize the control
  15. this.wrapCheckboxWithDivs();
  16. this.attachEvents();
  17. this.disableTextSelection();
  18. if (this.resizeHandle) { this.optionallyResize('handle'); }
  19. if (this.resizeContainer) { this.optionallyResize('container'); }
  20. this.initialPosition();
  21. };
  22. $.extend($[iphoneStyle].prototype, {
  23. // Wrap the existing input[type=checkbox] with divs for styling and grab DOM references to the created nodes
  24. wrapCheckboxWithDivs: function() {
  25. this.$elem.wrap('<div class="' + this.containerClass + '" />');
  26. this.container = this.$elem.parent();
  27. this.offLabel = $('<label class="'+ this.labelOffClass +'">' +
  28. '<span>'+ this.uncheckedLabel +'</span>' +
  29. '</label>').appendTo(this.container);
  30. this.offSpan = this.offLabel.children('span');
  31. this.onLabel = $('<label class="'+ this.labelOnClass +'">' +
  32. '<span>'+ this.checkedLabel +'</span>' +
  33. '</label>').appendTo(this.container);
  34. this.onSpan = this.onLabel.children('span');
  35. this.handle = $('<div class="' + this.handleClass + '">' +
  36. '</div>').appendTo(this.container);
  37. },
  38. // Disable IE text selection, other browsers are handled in CSS
  39. disableTextSelection: function() {
  40. if (!$.browser.msie) { return; }
  41. // Elements containing text should be unselectable
  42. $.each([this.handle, this.offLabel, this.onLabel, this.container], function(el) {
  43. $(el).attr("unselectable", "on");
  44. });
  45. },
  46. // Automatically resize the handle or container
  47. optionallyResize: function(mode) {
  48. var onLabelWidth = this.onLabel.width(),
  49. offLabelWidth = this.offLabel.width();
  50. if (mode == 'container') {
  51. var newWidth = (onLabelWidth > offLabelWidth) ? onLabelWidth : offLabelWidth;
  52. newWidth += this.handle.width();
  53. } else {
  54. var newWidth = (onLabelWidth < offLabelWidth) ? onLabelWidth : offLabelWidth;
  55. }
  56. this[mode].css({ width: newWidth });
  57. },
  58. attachEvents: function() {
  59. var obj = this;
  60. // A mousedown anywhere in the control will start tracking for dragging
  61. this.container
  62. .bind('mousedown touchstart', function(event) {
  63. event.preventDefault();
  64. if (obj.$elem.is(':disabled')) { return; }
  65. var x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  66. $[iphoneStyle].currentlyClicking = obj.handle;
  67. $[iphoneStyle].dragStartPosition = x;
  68. $[iphoneStyle].handleLeftOffset = parseInt(obj.handle.css('left'), 0) || 0;
  69. })
  70. // Utilize event bubbling to handle drag on any element beneath the container
  71. .bind('iPhoneDrag', function(event, x) {
  72. event.preventDefault();
  73. if (obj.$elem.is(':disabled')) { return; }
  74. var p = (x + $[iphoneStyle].handleLeftOffset - $[iphoneStyle].dragStartPosition) / obj.rightSide;
  75. if (p < 0) { p = 0; }
  76. if (p > 1) { p = 1; }
  77. obj.handle.css({ left: p * obj.rightSide });
  78. obj.onLabel.css({ width: p * obj.rightSide + 8 });
  79. obj.offSpan.css({ marginRight: -p * obj.rightSide });
  80. obj.onSpan.css({ marginLeft: -(1 - p) * obj.rightSide });
  81. })
  82. // Utilize event bubbling to handle drag end on any element beneath the container
  83. .bind('iPhoneDragEnd', function(event, x) {
  84. if (obj.$elem.is(':disabled')) { return; }
  85. if ($[iphoneStyle].dragging) {
  86. var p = (x - $[iphoneStyle].dragStartPosition) / obj.rightSide;
  87. obj.$elem.attr('checked', (p >= 0.5));
  88. } else {
  89. obj.$elem.attr('checked', !obj.$elem.attr('checked'));
  90. }
  91. $[iphoneStyle].currentlyClicking = null;
  92. $[iphoneStyle].dragging = null;
  93. obj.$elem.change();
  94. });
  95. // Animate when we get a change event
  96. this.$elem.change(function() {
  97. if (obj.$elem.is(':disabled')) {
  98. obj.container.addClass(obj.disabledClass);
  99. return false;
  100. } else {
  101. obj.container.removeClass(obj.disabledClass);
  102. }
  103. var new_left = obj.$elem.attr('checked') ? obj.rightSide : 0;
  104. new_left = new_left;
  105. if (new_left==26) {
  106. obj.onLabel.animate({ width: 40 }, obj.duration);
  107. obj.handle.animate({ left: 25 }, obj.duration);
  108. } else {
  109. obj.onLabel.animate({ width: 0 }, obj.duration);
  110. obj.handle.animate({ left: 1 }, obj.duration);
  111. }
  112. obj.offSpan.animate({ marginRight: -new_left }, obj.duration);
  113. obj.onSpan.animate({ marginLeft: new_left - obj.rightSide }, obj.duration);
  114. });
  115. },
  116. // Setup the control's inital position
  117. initialPosition: function() {
  118. this.offLabel.css({ width: this.container.width() -5 });
  119. var offset = ($.browser.msie && $.browser.version < 7) ? 3 : 6;
  120. this.rightSide = this.container.width() - this.handle.width() - offset;
  121. if (this.$elem.is(':checked')) {
  122. this.handle.css({ left: this.rightSide -1 });
  123. this.onLabel.css({ width: this.rightSide + 14 });
  124. this.offSpan.css({ marginRight: -this.rightSide });
  125. } else {
  126. this.onLabel.css({ width: 0 });
  127. this.onSpan.css({ marginLeft: -this.rightSide });
  128. }
  129. if (this.$elem.is(':disabled')) {
  130. this.container.addClass(this.disabledClass);
  131. }
  132. }
  133. });
  134. // jQuery-specific code
  135. $.fn[iphoneStyle] = function(options) {
  136. var checkboxes = this.filter(':checkbox');
  137. // Fail early if we don't have any checkboxes passed in
  138. if (!checkboxes.length) { return this; }
  139. // Merge options passed in with global defaults
  140. var opt = $.extend({}, $[iphoneStyle].defaults, options);
  141. checkboxes.each(function() {
  142. $(this).data(iphoneStyle, new $[iphoneStyle](this, opt));
  143. });
  144. if (!$[iphoneStyle].initComplete) {
  145. // As the mouse moves on the page, animate if we are in a drag state
  146. $(document)
  147. .bind('mousemove touchmove', function(event) {
  148. if (!$[iphoneStyle].currentlyClicking) { return; }
  149. event.preventDefault();
  150. var x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  151. if (!$[iphoneStyle].dragging &&
  152. (Math.abs($[iphoneStyle].dragStartPosition - x) > opt.dragThreshold)) {
  153. $[iphoneStyle].dragging = true;
  154. }
  155. $(event.target).trigger('iPhoneDrag', [x]);
  156. })
  157. // When the mouse comes up, leave drag state
  158. .bind('mouseup touchend', function(event) {
  159. if (!$[iphoneStyle].currentlyClicking) { return; }
  160. event.preventDefault();
  161. var x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  162. $($[iphoneStyle].currentlyClicking).trigger('iPhoneDragEnd', [x]);
  163. });
  164. $[iphoneStyle].initComplete = true;
  165. }
  166. return this;
  167. }; // End of $.fn[iphoneStyle]
  168. $[iphoneStyle].defaults = {
  169. duration: 200, // Time spent during slide animation
  170. checkedLabel: 'on', // Text content of "on" state
  171. uncheckedLabel: 'off', // Text content of "off" state
  172. resizeHandle: false, // Automatically resize the handle to cover either label
  173. resizeContainer: true, // Automatically resize the widget to contain the labels
  174. disabledClass: 'iPhoneCheckDisabled',
  175. containerClass: 'iPhoneCheckContainer',
  176. labelOnClass: 'iPhoneCheckLabelOn',
  177. labelOffClass: 'iPhoneCheckLabelOff',
  178. handleClass: 'iPhoneCheckHandle',
  179. handleCenterClass: 'iPhoneCheckHandleCenter',
  180. handleRightClass: 'iPhoneCheckHandleRight',
  181. dragThreshold: 5 // Pixels that must be dragged for a click to be ignored
  182. };
  183. })(jQuery, 'iphoneStyle');