jquery.switchButton.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /**
  2. * jquery.switchButton.js v1.0
  3. * jQuery iPhone-like switch button
  4. * @author Olivier Lance <olivier.lance@sylights.com>
  5. *
  6. * Copyright (c) Olivier Lance - released under MIT License {{{
  7. *
  8. * Permission is hereby granted, free of charge, to any person
  9. * obtaining a copy of this software and associated documentation
  10. * files (the "Software"), to deal in the Software without
  11. * restriction, including without limitation the rights to use,
  12. * copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the
  14. * Software is furnished to do so, subject to the following
  15. * conditions:
  16. * The above copyright notice and this permission notice shall be
  17. * included in all copies or substantial portions of the Software.
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  19. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  20. * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  21. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  22. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  23. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  24. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  25. * OTHER DEALINGS IN THE SOFTWARE.
  26. * }}}
  27. */
  28. /*
  29. * Meant to be used on a <input type="checkbox">, this widget will replace the receiver element with an iPhone-style
  30. * switch button with two states: "on" and "off".
  31. * Labels of the states are customizable, as are their presence and position. The receiver element's "checked" attribute
  32. * is updated according to the state of the switch, so that it can be used in a <form>.
  33. *
  34. */
  35. (function($) {
  36. $.widget("sylightsUI.switchButton", {
  37. options: {
  38. checked: undefined, // State of the switch
  39. show_labels: true, // Should we show the on and off labels?
  40. labels_placement: "both", // Position of the labels: "both", "left" or "right"
  41. on_label: "ON", // Text to be displayed when checked
  42. off_label: "OFF", // Text to be displayed when unchecked
  43. width: 25, // Width of the button in pixels
  44. height: 11, // Height of the button in pixels
  45. button_width: 12, // Width of the sliding part in pixels
  46. clear: true, // Should we insert a div with style="clear: both;" after the switch button?
  47. clear_after: null, // Override the element after which the clearing div should be inserted (null > right after the button)
  48. on_callback: undefined, //callback function that will be executed after going to on state
  49. off_callback: undefined //callback function that will be executed after going to off state
  50. },
  51. _create: function() {
  52. // Init the switch from the checkbox if no state was specified on creation
  53. if (this.options.checked === undefined) {
  54. this.options.checked = this.element.prop("checked");
  55. }
  56. this._initLayout();
  57. this._initEvents();
  58. },
  59. _initLayout: function() {
  60. // Hide the receiver element
  61. this.element.hide();
  62. // Create our objects: two labels and the button
  63. this.off_label = $("<span>").addClass("switch-button-label");
  64. this.on_label = $("<span>").addClass("switch-button-label");
  65. this.button_bg = $("<div>").addClass("switch-button-background");
  66. this.button = $("<div>").addClass("switch-button-button");
  67. // Insert the objects into the DOM
  68. this.off_label.insertAfter(this.element);
  69. this.button_bg.insertAfter(this.off_label);
  70. this.on_label.insertAfter(this.button_bg);
  71. this.button_bg.append(this.button);
  72. // Insert a clearing element after the specified element if needed
  73. if(this.options.clear)
  74. {
  75. if (this.options.clear_after === null) {
  76. this.options.clear_after = this.on_label;
  77. }
  78. $("<div>").css({
  79. clear: "left"
  80. }).insertAfter(this.options.clear_after);
  81. }
  82. // Call refresh to update labels text and visibility
  83. this._refresh();
  84. // Init labels and switch state
  85. // This will animate all checked switches to the ON position when
  86. // loading... this is intentional!
  87. this.options.checked = !this.options.checked;
  88. this._toggleSwitch(true);
  89. },
  90. _refresh: function() {
  91. // Refresh labels display
  92. if (this.options.show_labels) {
  93. this.off_label.show();
  94. this.on_label.show();
  95. }
  96. else {
  97. this.off_label.hide();
  98. this.on_label.hide();
  99. }
  100. // Move labels around depending on labels_placement option
  101. switch(this.options.labels_placement) {
  102. case "both":
  103. {
  104. // Don't move anything if labels are already in place
  105. if(this.button_bg.prev() !== this.off_label || this.button_bg.next() !== this.on_label)
  106. {
  107. // Detach labels form DOM and place them correctly
  108. this.off_label.detach();
  109. this.on_label.detach();
  110. this.off_label.insertBefore(this.button_bg);
  111. this.on_label.insertAfter(this.button_bg);
  112. // Update label classes
  113. this.on_label.addClass(this.options.checked ? "on" : "off").removeClass(this.options.checked ? "off" : "on");
  114. this.off_label.addClass(this.options.checked ? "off" : "on").removeClass(this.options.checked ? "on" : "off");
  115. }
  116. break;
  117. }
  118. case "left":
  119. {
  120. // Don't move anything if labels are already in place
  121. if(this.button_bg.prev() !== this.on_label || this.on_label.prev() !== this.off_label)
  122. {
  123. // Detach labels form DOM and place them correctly
  124. this.off_label.detach();
  125. this.on_label.detach();
  126. this.off_label.insertBefore(this.button_bg);
  127. this.on_label.insertBefore(this.button_bg);
  128. // update label classes
  129. this.on_label.addClass("on").removeClass("off");
  130. this.off_label.addClass("off").removeClass("on");
  131. }
  132. break;
  133. }
  134. case "right":
  135. {
  136. // Don't move anything if labels are already in place
  137. if(this.button_bg.next() !== this.off_label || this.off_label.next() !== this.on_label)
  138. {
  139. // Detach labels form DOM and place them correctly
  140. this.off_label.detach();
  141. this.on_label.detach();
  142. this.off_label.insertAfter(this.button_bg);
  143. this.on_label.insertAfter(this.off_label);
  144. // update label classes
  145. this.on_label.addClass("on").removeClass("off");
  146. this.off_label.addClass("off").removeClass("on");
  147. }
  148. break;
  149. }
  150. }
  151. // Refresh labels texts
  152. this.on_label.html(this.options.on_label);
  153. this.off_label.html(this.options.off_label);
  154. // Refresh button's dimensions
  155. this.button_bg.width(this.options.width);
  156. this.button_bg.height(this.options.height);
  157. this.button.width(this.options.button_width);
  158. this.button.height(this.options.height);
  159. },
  160. _initEvents: function() {
  161. var self = this;
  162. // Toggle switch when the switch is clicked
  163. this.button_bg.click(function(e) {
  164. e.preventDefault();
  165. e.stopPropagation();
  166. self._toggleSwitch(false);
  167. return false;
  168. });
  169. this.button.click(function(e) {
  170. e.preventDefault();
  171. e.stopPropagation();
  172. self._toggleSwitch(false);
  173. return false;
  174. });
  175. // Set switch value when clicking labels
  176. this.on_label.click(function(e) {
  177. if (self.options.checked && self.options.labels_placement === "both") {
  178. return false;
  179. }
  180. self._toggleSwitch(false);
  181. return false;
  182. });
  183. this.off_label.click(function(e) {
  184. if (!self.options.checked && self.options.labels_placement === "both") {
  185. return false;
  186. }
  187. self._toggleSwitch(false);
  188. return false;
  189. });
  190. },
  191. _setOption: function(key, value) {
  192. if (key === "checked") {
  193. this._setChecked(value);
  194. return;
  195. }
  196. this.options[key] = value;
  197. this._refresh();
  198. },
  199. _setChecked: function(value) {
  200. if (value === this.options.checked) {
  201. return;
  202. }
  203. this.options.checked = !value;
  204. this._toggleSwitch(false);
  205. },
  206. _toggleSwitch: function(isInitializing) {
  207. // Don't toggle the switch if it is set to readonly or disabled, unless it is initializing and animating itself
  208. if( !isInitializing && (this.element.attr('readonly') == 'readonly' || this.element.prop('disabled')) )
  209. return;
  210. this.options.checked = !this.options.checked;
  211. var newLeft = "";
  212. if (this.options.checked) {
  213. // Update the underlying checkbox state
  214. this.element.prop("checked", true);
  215. this.element.change();
  216. var dLeft = this.options.width - this.options.button_width;
  217. newLeft = "+=" + dLeft;
  218. // Update labels states
  219. if(this.options.labels_placement == "both")
  220. {
  221. this.off_label.removeClass("on").addClass("off");
  222. this.on_label.removeClass("off").addClass("on");
  223. }
  224. else
  225. {
  226. this.off_label.hide();
  227. this.on_label.show();
  228. }
  229. this.button_bg.addClass("checked");
  230. //execute on state callback if its supplied
  231. if(typeof this.options.on_callback === 'function') this.options.on_callback.call(this);
  232. }
  233. else {
  234. // Update the underlying checkbox state
  235. this.element.prop("checked", false);
  236. this.element.change();
  237. newLeft = "-1px";
  238. // Update labels states
  239. if(this.options.labels_placement == "both")
  240. {
  241. this.off_label.removeClass("off").addClass("on");
  242. this.on_label.removeClass("on").addClass("off");
  243. }
  244. else
  245. {
  246. this.off_label.show();
  247. this.on_label.hide();
  248. }
  249. this.button_bg.removeClass("checked");
  250. //execute off state callback if its supplied
  251. if(typeof this.options.off_callback === 'function') this.options.off_callback.call(this);
  252. }
  253. // Animate the switch
  254. this.button.animate({ left: newLeft }, 250, "easeInOutCubic");
  255. }
  256. });
  257. })(jQuery);