super_slider.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /**
  2. * Super Slider
  3. * Last update: 12 Jan 2011
  4. *
  5. * Changelog:
  6. * 0.1 - Initial release
  7. *
  8. *
  9. * Copyright (c) 2011 Tom Beddard
  10. * http://www.subblue.com
  11. *
  12. * Released under the MIT License:
  13. * http://www.opensource.org/licenses/mit-license.php
  14. */
  15. /*global window, $, document*/
  16. function SuperSlider(id, opts) {
  17. this.id = id;
  18. this.init(opts);
  19. return this.slider;
  20. }
  21. SuperSlider.prototype = {
  22. init: function (opts) {
  23. var self = this,
  24. opt,
  25. style;
  26. this.options = {
  27. slider_class : 'ss_slider', // slider class name
  28. track_class : 'ss_track', // track class name
  29. handle_class : 'ss_handle', // handle class name
  30. input_class : 'ss_input', // input class name
  31. label_class : 'ss_label', // label class name
  32. min : 0, // minimum value
  33. max : 1, // maximum value
  34. step : 0.01, // step increment
  35. default_value : 0.5, // default value
  36. value : 0.5, // current value
  37. decimal_places : 3, // decimal place rounding
  38. label : null, // label
  39. name : null // input name attribute
  40. };
  41. for (opt in opts) {
  42. if (opts.hasOwnProperty.call(opts, opt)) {
  43. this.options[opt] = opts[opt];
  44. }
  45. }
  46. // Build slider
  47. this.slider = $("<div>")
  48. .addClass(this.options.slider_class)
  49. .css("webkitUserSelect", 'none');
  50. this.track = $("<div>").addClass(this.options.track_class);
  51. this.handle = $("<div>").addClass(this.options.handle_class);
  52. this.input = $("<input>")
  53. .attr("type", "input")
  54. .addClass(this.options.input_class)
  55. .attr("value", this.options.value);
  56. if (this.options.name) {
  57. this.input.attr("name", this.options.name);
  58. }
  59. if (this.options.label) {
  60. this.label = $("<label>")
  61. .text(this.options.label)
  62. .addClass(this.options.label_class)
  63. .attr("title", "Click to reset to default");
  64. this.slider.append(this.label.get(0));
  65. }
  66. this.track.append(this.handle.get(0));
  67. this.slider.append(this.track.get(0));
  68. this.slider.append(this.input.get(0));
  69. // Define event listeners
  70. this.mouseDown = function (event) {
  71. self.startDrag(event);
  72. };
  73. this.mouseMove = function (event) {
  74. self.onDrag(event);
  75. };
  76. this.mouseUp = function (event) {
  77. self.endDrag(event);
  78. };
  79. this.mouseOver = function (event) {
  80. self.infocus = true;
  81. event.stopPropagation();
  82. };
  83. this.mouseOut = function (event) {
  84. self.infocus = false;
  85. event.stopPropagation();
  86. };
  87. this.keyDown = function (event) {
  88. self.keyPress(event);
  89. };
  90. this.startEdit = function (event) {
  91. self.editing = true;
  92. };
  93. this.endEdit = function (event) {
  94. self.value(self.input.get(0).value);
  95. self.editing = false;
  96. };
  97. this.labelClick = function (event) {
  98. self.updateSlider(self.options.default_value);
  99. self.input.trigger("reset");
  100. return false;
  101. };
  102. this.slider.bind("DOMNodeInserted", function (event) {
  103. // We only want to call sliderInserted once, but Chrome
  104. // won't attach bindings or update the slider until it's
  105. // parented
  106. var really_inserted = (self.slider.parent().length != 0);
  107. if (!self.initialised && really_inserted) {
  108. self.sliderInserted(event);
  109. self.initialised = true;
  110. }
  111. });
  112. this.infocus = false;
  113. },
  114. // Slider inserted into the DOM and ready for action
  115. sliderInserted: function (event) {
  116. // Get computed style dimensions
  117. this.handle_width = parseInt(window.getComputedStyle(this.handle.get(0), null).getPropertyValue("width"), 10);
  118. this.track_width = parseInt(window.getComputedStyle(this.track.get(0), null).getPropertyValue("width"), 10) - this.handle_width;
  119. // Setup current value
  120. this.updateSlider(this.options.value);
  121. if (this.label) {
  122. this.label.bind("click", this.labelClick);
  123. }
  124. this.track.bind("mousedown", this.mouseDown);
  125. this.slider.bind("mouseover", this.mouseOver);
  126. this.slider.bind("mouseout", this.mouseOut);
  127. this.input.bind("blur", this.endEdit);
  128. document.addEventListener("keydown", this.keyDown, false);
  129. },
  130. value: function (v) {
  131. if (typeof v !== "undefined") {
  132. this.updateSlider(v);
  133. }
  134. return this.val;
  135. },
  136. // Update the slider and fire the change event
  137. updateSlider: function (value) {
  138. var v = Math.min(Math.max(value, this.options.min), this.options.max),
  139. pos = this.track_width * ((v - this.options.min) / (this.options.max - this.options.min)),
  140. val = this.round(parseFloat(v), this.options.decimal_places);
  141. if (isNaN(val) || value === '') {
  142. // Invalid number, reset
  143. this.input.attr("value", this.val);
  144. this.input.get(0).value = this.val;
  145. } else {
  146. // Valid number
  147. this.handle.css("left", pos + "px");
  148. this.val = val;
  149. this.input.attr("value", this.val);
  150. this.input.get(0).value = this.val; // This way too therwise the field doesn't always update
  151. this.input.get(0).val = this.val;
  152. if (this.initialised && this.old_val !== this.val) {
  153. this.input.trigger("change");
  154. this.old_val = this.val;
  155. }
  156. }
  157. },
  158. startDrag: function (event) {
  159. // event.preventDefault();
  160. if (event.currentTarget === event.target) {
  161. // Clicked track
  162. this.ox = this.handle_width / 2;
  163. this.onDrag(event);
  164. } else {
  165. // Clicked handle
  166. this.ox = event.offsetX || event.layerX;
  167. }
  168. this.track.get(0).addEventListener("mousemove", this.mouseMove, false);
  169. $("body").bind("mouseup", this.mouseUp);
  170. },
  171. onDrag: function (event) {
  172. var x, v;
  173. if (event.currentTarget === event.target) {
  174. // track clicked
  175. x = event.offsetX || event.layerX;
  176. } else {
  177. // handle clicked
  178. x = event.target.offsetLeft + (event.offsetX || event.layerX);
  179. }
  180. x -= this.ox;
  181. v = (x / this.track_width) * (this.options.max - this.options.min);
  182. if (!event.altKey) {
  183. // Snap to step increments
  184. v = Math.floor(v / this.options.step) * this.options.step;
  185. }
  186. this.updateSlider(this.options.min + v);
  187. },
  188. endDrag: function (event) {
  189. this.track.get(0).removeEventListener("mousemove", this.mouseMove, false);
  190. $("body").unbind("mouseup", this.mouseUp);
  191. },
  192. keyPress: function (event) {
  193. var delta = 0,
  194. mult = 1;
  195. if (this.infocus) {
  196. // console.log("key", event);
  197. if (event.shiftKey && event.altKey) {
  198. mult = 0.01;
  199. } else if (event.altKey) {
  200. mult = 0.1;
  201. } else if (event.shiftKey) {
  202. mult = 10;
  203. }
  204. switch (event.keyCode) {
  205. case 38:
  206. // Up arrow 38 - event.DOM_VK_UP
  207. this.value(this.val += this.options.step * mult);
  208. break;
  209. case 40:
  210. // Down arrow 40 - event.DOM_VK_DOWN
  211. this.value(this.val -= this.options.step * mult);
  212. break;
  213. case 13:
  214. // Enter pressed 13 - event.DOM_VK_RETURN
  215. this.input.get(0).blur();
  216. return false;
  217. }
  218. }
  219. return true;
  220. },
  221. round: function (v, n) {
  222. var exp = Math.pow(10, n || 1);
  223. return Math.round(v * exp) / exp;
  224. }
  225. };