JQuerySpinBtn.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*globals $, svgEditor*/
  2. /*jslint vars: true, eqeq: true*/
  3. /* SpinButton control
  4. *
  5. * Adds bells and whistles to any ordinary textbox to
  6. * make it look and feel like a SpinButton Control.
  7. *
  8. * Originally written by George Adamson, Software Unity (george.jquery@softwareunity.com) August 2006.
  9. * - Added min/max options
  10. * - Added step size option
  11. * - Added bigStep (page up/down) option
  12. *
  13. * Modifications made by Mark Gibson, (mgibson@designlinks.net) September 2006:
  14. * - Converted to jQuery plugin
  15. * - Allow limited or unlimited min/max values
  16. * - Allow custom class names, and add class to input element
  17. * - Removed global vars
  18. * - Reset (to original or through config) when invalid value entered
  19. * - Repeat whilst holding mouse button down (with initial pause, like keyboard repeat)
  20. * - Support mouse wheel in Firefox
  21. * - Fix double click in IE
  22. * - Refactored some code and renamed some vars
  23. *
  24. * Modifications by Jeff Schiller, June 2009:
  25. * - provide callback function for when the value changes based on the following
  26. * http://www.mail-archive.com/jquery-en@googlegroups.com/msg36070.html
  27. * Modifications by Jeff Schiller, July 2009:
  28. * - improve styling for widget in Opera
  29. * - consistent key-repeat handling cross-browser
  30. * Modifications by Alexis Deveria, October 2009:
  31. * - provide "stepfunc" callback option to allow custom function to run when changing a value
  32. * - Made adjustValue(0) only run on certain keyup events, not all.
  33. *
  34. * Tested in IE6, Opera9, Firefox 1.5
  35. * v1.0 11 Aug 2006 - George Adamson - First release
  36. * v1.1 Aug 2006 - George Adamson - Minor enhancements
  37. * v1.2 27 Sep 2006 - Mark Gibson - Major enhancements
  38. * v1.3a 28 Sep 2006 - George Adamson - Minor enhancements
  39. * v1.4 18 Jun 2009 - Jeff Schiller - Added callback function
  40. * v1.5 06 Jul 2009 - Jeff Schiller - Fixes for Opera.
  41. * v1.6 13 Oct 2009 - Alexis Deveria - Added stepfunc function
  42. * v1.7 21 Oct 2009 - Alexis Deveria - Minor fixes
  43. * Fast-repeat for keys and live updating as you type.
  44. * v1.8 12 Jan 2010 - Benjamin Thomas - Fixes for mouseout behavior.
  45. * Added smallStep
  46. Sample usage:
  47. // Create group of settings to initialise spinbutton(s). (Optional)
  48. var myOptions = {
  49. min: 0, // Set lower limit.
  50. max: 100, // Set upper limit.
  51. step: 1, // Set increment size.
  52. smallStep: 0.5, // Set shift-click increment size.
  53. spinClass: mySpinBtnClass, // CSS class to style the spinbutton. (Class also specifies url of the up/down button image.)
  54. upClass: mySpinUpClass, // CSS class for style when mouse over up button.
  55. downClass: mySpinDnClass // CSS class for style when mouse over down button.
  56. }
  57. $(document).ready(function(){
  58. // Initialise INPUT element(s) as SpinButtons: (passing options if desired)
  59. $("#myInputElement").SpinButton(myOptions);
  60. });
  61. */
  62. $.fn.SpinButton = function(cfg) { 'use strict';
  63. function coord(el,prop) {
  64. var c = el[prop], b = document.body;
  65. while ((el = el.offsetParent) && (el != b)) {
  66. if (!$.browser.msie || (el.currentStyle.position !== 'relative')) {
  67. c += el[prop];
  68. }
  69. }
  70. return c;
  71. }
  72. return this.each(function(){
  73. this.repeating = false;
  74. // Apply specified options or defaults:
  75. // (Ought to refactor this some day to use $.extend() instead)
  76. this.spinCfg = {
  77. //min: cfg && cfg.min ? Number(cfg.min) : null,
  78. //max: cfg && cfg.max ? Number(cfg.max) : null,
  79. min: cfg && !isNaN(parseFloat(cfg.min)) ? Number(cfg.min) : null, // Fixes bug with min:0
  80. max: cfg && !isNaN(parseFloat(cfg.max)) ? Number(cfg.max) : null,
  81. step: cfg && cfg.step ? Number(cfg.step) : 1,
  82. stepfunc: cfg && cfg.stepfunc ? cfg.stepfunc : false,
  83. page: cfg && cfg.page ? Number(cfg.page) : 10,
  84. upClass: cfg && cfg.upClass ? cfg.upClass : 'up',
  85. downClass: cfg && cfg.downClass ? cfg.downClass : 'down',
  86. reset: cfg && cfg.reset ? cfg.reset : this.value,
  87. delay: cfg && cfg.delay ? Number(cfg.delay) : 500,
  88. interval: cfg && cfg.interval ? Number(cfg.interval) : 100,
  89. _btn_width: 20,
  90. _direction: null,
  91. _delay: null,
  92. _repeat: null,
  93. callback: cfg && cfg.callback ? cfg.callback : null
  94. };
  95. // if a smallStep isn't supplied, use half the regular step
  96. this.spinCfg.smallStep = cfg && cfg.smallStep ? cfg.smallStep : this.spinCfg.step/2;
  97. this.adjustValue = function(i){
  98. var v;
  99. if(isNaN(this.value)) {
  100. v = this.spinCfg.reset;
  101. } else if($.isFunction(this.spinCfg.stepfunc)) {
  102. v = this.spinCfg.stepfunc(this, i);
  103. } else {
  104. // weirdest javascript bug ever: 5.1 + 0.1 = 5.199999999
  105. v = Number((Number(this.value) + Number(i)).toFixed(5));
  106. }
  107. if (this.spinCfg.min !== null) {v = Math.max(v, this.spinCfg.min);}
  108. if (this.spinCfg.max !== null) {v = Math.min(v, this.spinCfg.max);}
  109. this.value = v;
  110. if ($.isFunction(this.spinCfg.callback)) {this.spinCfg.callback(this);}
  111. };
  112. $(this)
  113. .addClass(cfg && cfg.spinClass ? cfg.spinClass : 'spin-button')
  114. .mousemove(function(e){
  115. // Determine which button mouse is over, or not (spin direction):
  116. var x = e.pageX || e.x;
  117. var y = e.pageY || e.y;
  118. var el = e.target || e.srcElement;
  119. var scale = svgEditor.tool_scale || 1;
  120. var height = $(el).height()/2;
  121. var direction =
  122. (x > coord(el,'offsetLeft') + el.offsetWidth*scale - this.spinCfg._btn_width)
  123. ? ((y < coord(el,'offsetTop') + height*scale) ? 1 : -1) : 0;
  124. if (direction !== this.spinCfg._direction) {
  125. // Style up/down buttons:
  126. switch(direction){
  127. case 1: // Up arrow:
  128. $(this).removeClass(this.spinCfg.downClass).addClass(this.spinCfg.upClass);
  129. break;
  130. case -1: // Down arrow:
  131. $(this).removeClass(this.spinCfg.upClass).addClass(this.spinCfg.downClass);
  132. break;
  133. default: // Mouse is elsewhere in the textbox
  134. $(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass);
  135. }
  136. // Set spin direction:
  137. this.spinCfg._direction = direction;
  138. }
  139. })
  140. .mouseout(function(){
  141. // Reset up/down buttons to their normal appearance when mouse moves away:
  142. $(this).removeClass(this.spinCfg.upClass).removeClass(this.spinCfg.downClass);
  143. this.spinCfg._direction = null;
  144. window.clearInterval(this.spinCfg._repeat);
  145. window.clearTimeout(this.spinCfg._delay);
  146. })
  147. .mousedown(function(e){
  148. if (e.button === 0 && this.spinCfg._direction != 0) {
  149. // Respond to click on one of the buttons:
  150. var self = this;
  151. var stepSize = e.shiftKey ? self.spinCfg.smallStep : self.spinCfg.step;
  152. var adjust = function() {
  153. self.adjustValue(self.spinCfg._direction * stepSize);
  154. };
  155. adjust();
  156. // Initial delay before repeating adjustment
  157. self.spinCfg._delay = window.setTimeout(function() {
  158. adjust();
  159. // Repeat adjust at regular intervals
  160. self.spinCfg._repeat = window.setInterval(adjust, self.spinCfg.interval);
  161. }, self.spinCfg.delay);
  162. }
  163. })
  164. .mouseup(function(e){
  165. // Cancel repeating adjustment
  166. window.clearInterval(this.spinCfg._repeat);
  167. window.clearTimeout(this.spinCfg._delay);
  168. })
  169. .dblclick(function(e) {
  170. if ($.browser.msie) {
  171. this.adjustValue(this.spinCfg._direction * this.spinCfg.step);
  172. }
  173. })
  174. .keydown(function(e){
  175. // Respond to up/down arrow keys.
  176. switch(e.keyCode){
  177. case 38: this.adjustValue(this.spinCfg.step); break; // Up
  178. case 40: this.adjustValue(-this.spinCfg.step); break; // Down
  179. case 33: this.adjustValue(this.spinCfg.page); break; // PageUp
  180. case 34: this.adjustValue(-this.spinCfg.page); break; // PageDown
  181. }
  182. })
  183. /*
  184. http://unixpapa.com/js/key.html describes the current state-of-affairs for
  185. key repeat events:
  186. - Safari 3.1 changed their model so that keydown is reliably repeated going forward
  187. - Firefox and Opera still only repeat the keypress event, not the keydown
  188. */
  189. .keypress(function(e){
  190. if (this.repeating) {
  191. // Respond to up/down arrow keys.
  192. switch(e.keyCode){
  193. case 38: this.adjustValue(this.spinCfg.step); break; // Up
  194. case 40: this.adjustValue(-this.spinCfg.step); break; // Down
  195. case 33: this.adjustValue(this.spinCfg.page); break; // PageUp
  196. case 34: this.adjustValue(-this.spinCfg.page); break; // PageDown
  197. }
  198. }
  199. // we always ignore the first keypress event (use the keydown instead)
  200. else {
  201. this.repeating = true;
  202. }
  203. })
  204. // clear the 'repeating' flag
  205. .keyup(function(e) {
  206. this.repeating = false;
  207. switch(e.keyCode){
  208. case 38: // Up
  209. case 40: // Down
  210. case 33: // PageUp
  211. case 34: // PageDown
  212. case 13: this.adjustValue(0); break; // Enter/Return
  213. }
  214. })
  215. .bind("mousewheel", function(e){
  216. // Respond to mouse wheel in IE. (It returns up/dn motion in multiples of 120)
  217. if (e.wheelDelta >= 120) {
  218. this.adjustValue(this.spinCfg.step);
  219. }
  220. else if (e.wheelDelta <= -120) {
  221. this.adjustValue(-this.spinCfg.step);
  222. }
  223. e.preventDefault();
  224. })
  225. .change(function(e){
  226. this.adjustValue(0);
  227. });
  228. if (this.addEventListener) {
  229. // Respond to mouse wheel in Firefox
  230. this.addEventListener('DOMMouseScroll', function(e) {
  231. if (e.detail > 0) {
  232. this.adjustValue(-this.spinCfg.step);
  233. }
  234. else if (e.detail < 0) {
  235. this.adjustValue(this.spinCfg.step);
  236. }
  237. e.preventDefault();
  238. }, false);
  239. }
  240. });
  241. };