jquery.range.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /*jshint multistr:true, curly: false */
  2. /*global jQuery:false, define: false */
  3. /**
  4. * jRange - Awesome range control
  5. *
  6. * Written by
  7. * ----------
  8. * Nitin Hayaran (nitinhayaran@gmail.com)
  9. *
  10. * Licensed under the MIT (MIT-LICENSE.txt).
  11. *
  12. * @author Nitin Hayaran
  13. * @version 0.1-RELEASE
  14. *
  15. * Dependencies
  16. * ------------
  17. * jQuery (http://jquery.com)
  18. *
  19. **/
  20. ;
  21. (function($, window, document, undefined) {
  22. 'use strict';
  23. var jRange = function() {
  24. return this.init.apply(this, arguments);
  25. };
  26. jRange.prototype = {
  27. defaults: {
  28. onstatechange: function() {},
  29. isRange: false,
  30. showLabels: true,
  31. showScale: true,
  32. step: 1,
  33. format: '%s',
  34. theme: 'theme-green',
  35. width: 300,
  36. disable: false
  37. },
  38. template: '<div class="slider-container">\
  39. <div class="back-bar">\
  40. <div class="selected-bar"></div>\
  41. <div class="pointer low"></div><div class="pointer-label">123456</div>\
  42. <div class="pointer high"></div><div class="pointer-label">456789</div>\
  43. <div class="clickable-dummy"></div>\
  44. </div>\
  45. <div class="scale"></div>\
  46. </div>',
  47. init: function(node, options) {
  48. this.options = $.extend({}, this.defaults, options);
  49. this.inputNode = $(node);
  50. this.options.value = this.inputNode.val() || (this.options.isRange ? this.options.from + ',' + this.options.from : this.options.from);
  51. this.domNode = $(this.template);
  52. this.domNode.addClass(this.options.theme);
  53. this.inputNode.after(this.domNode);
  54. this.domNode.on('change', this.onChange);
  55. this.pointers = $('.pointer', this.domNode);
  56. this.lowPointer = this.pointers.first();
  57. this.highPointer = this.pointers.last();
  58. this.labels = $('.pointer-label', this.domNode);
  59. this.lowLabel = this.labels.first();
  60. this.highLabel = this.labels.last();
  61. this.scale = $('.scale', this.domNode);
  62. this.bar = $('.selected-bar', this.domNode);
  63. this.clickableBar = this.domNode.find('.clickable-dummy');
  64. this.interval = this.options.to - this.options.from;
  65. this.render();
  66. },
  67. render: function() {
  68. // Check if inputNode is visible, and have some width, so that we can set slider width accordingly.
  69. if (this.inputNode.width() === 0 && !this.options.width) {
  70. console.log('jRange : no width found, returning');
  71. return;
  72. } else {
  73. this.domNode.width(this.options.width || this.inputNode.width());
  74. this.inputNode.hide();
  75. }
  76. if (this.isSingle()) {
  77. this.lowPointer.hide();
  78. this.lowLabel.hide();
  79. }
  80. if (!this.options.showLabels) {
  81. this.labels.hide();
  82. }
  83. this.attachEvents();
  84. if (this.options.showScale) {
  85. this.renderScale();
  86. }
  87. this.setValue(this.options.value);
  88. },
  89. isSingle: function() {
  90. if (typeof(this.options.value) === 'number') {
  91. return true;
  92. }
  93. return (this.options.value.indexOf(',') !== -1 || this.options.isRange) ?
  94. false : true;
  95. },
  96. attachEvents: function() {
  97. this.clickableBar.click($.proxy(this.barClicked, this));
  98. this.pointers.on('mousedown touchstart', $.proxy(this.onDragStart, this));
  99. this.pointers.bind('dragstart', function(event) {
  100. event.preventDefault();
  101. });
  102. },
  103. onDragStart: function(e) {
  104. if ( this.options.disable || (e.type === 'mousedown' && e.which !== 1)) {
  105. return;
  106. }
  107. e.stopPropagation();
  108. e.preventDefault();
  109. var pointer = $(e.target);
  110. this.pointers.removeClass('last-active');
  111. pointer.addClass('focused last-active');
  112. this[(pointer.hasClass('low') ? 'low' : 'high') + 'Label'].addClass('focused');
  113. $(document).on('mousemove.slider touchmove.slider', $.proxy(this.onDrag, this, pointer));
  114. $(document).on('mouseup.slider touchend.slider touchcancel.slider', $.proxy(this.onDragEnd, this));
  115. },
  116. onDrag: function(pointer, e) {
  117. e.stopPropagation();
  118. e.preventDefault();
  119. if (e.originalEvent.touches && e.originalEvent.touches.length) {
  120. e = e.originalEvent.touches[0];
  121. } else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
  122. e = e.originalEvent.changedTouches[0];
  123. }
  124. var position = e.clientX - this.domNode.offset().left;
  125. this.domNode.trigger('change', [this, pointer, position]);
  126. },
  127. onDragEnd: function(e) {
  128. this.pointers.removeClass('focused');
  129. this.labels.removeClass('focused');
  130. $(document).off('.slider');
  131. },
  132. barClicked: function(e) {
  133. if(this.options.disable) return;
  134. var x = e.pageX - this.clickableBar.offset().left;
  135. if (this.isSingle())
  136. this.setPosition(this.pointers.last(), x, true, true);
  137. else {
  138. var pointer = Math.abs(parseInt(this.pointers.first().css('left'), 10) - x + this.pointers.first().width() / 2) < Math.abs(parseInt(this.pointers.last().css('left'), 10) - x + this.pointers.first().width() / 2) ?
  139. this.pointers.first() : this.pointers.last();
  140. this.setPosition(pointer, x, true, true);
  141. }
  142. },
  143. onChange: function(e, self, pointer, position) {
  144. var min, max;
  145. if (self.isSingle()) {
  146. min = 0;
  147. max = self.domNode.width();
  148. } else {
  149. min = pointer.hasClass('high') ? self.lowPointer.position().left + self.lowPointer.width() / 2 : 0;
  150. max = pointer.hasClass('low') ? self.highPointer.position().left + self.highPointer.width() / 2 : self.domNode.width();
  151. }
  152. var value = Math.min(Math.max(position, min), max);
  153. self.setPosition(pointer, value, true);
  154. },
  155. setPosition: function(pointer, position, isPx, animate) {
  156. var leftPos,
  157. lowPos = this.lowPointer.position().left,
  158. highPos = this.highPointer.position().left,
  159. circleWidth = this.highPointer.width() / 2;
  160. if (!isPx) {
  161. position = this.prcToPx(position);
  162. }
  163. if (pointer[0] === this.highPointer[0]) {
  164. highPos = Math.round(position - circleWidth);
  165. } else {
  166. lowPos = Math.round(position - circleWidth);
  167. }
  168. pointer[animate ? 'animate' : 'css']({
  169. 'left': Math.round(position - circleWidth)
  170. });
  171. if (this.isSingle()) {
  172. leftPos = 0;
  173. } else {
  174. leftPos = lowPos + circleWidth;
  175. }
  176. this.bar[animate ? 'animate' : 'css']({
  177. 'width': Math.round(highPos + circleWidth - leftPos),
  178. 'left': leftPos
  179. });
  180. this.showPointerValue(pointer, position, animate);
  181. this.isReadonly();
  182. },
  183. // will be called from outside
  184. setValue: function(value) {
  185. var values = value.toString().split(',');
  186. this.options.value = value;
  187. var prc = this.valuesToPrc(values.length === 2 ? values : [0, values[0]]);
  188. if (this.isSingle()) {
  189. this.setPosition(this.highPointer, prc[1]);
  190. } else {
  191. this.setPosition(this.lowPointer, prc[0]);
  192. this.setPosition(this.highPointer, prc[1]);
  193. }
  194. },
  195. renderScale: function() {
  196. var s = this.options.scale || [this.options.from, this.options.to];
  197. var prc = Math.round((100 / (s.length - 1)) * 10) / 10;
  198. var str = '';
  199. for (var i = 0; i < s.length; i++) {
  200. str += '<span style="left: ' + i * prc + '%">' + (s[i] != '|' ? '<ins>' + s[i] + '</ins>' : '') + '</span>';
  201. }
  202. this.scale.html(str);
  203. $('ins', this.scale).each(function() {
  204. $(this).css({
  205. marginLeft: -$(this).outerWidth() / 2
  206. });
  207. });
  208. },
  209. getBarWidth: function() {
  210. var values = this.options.value.split(',');
  211. if (values.length > 1) {
  212. return parseInt(values[1], 10) - parseInt(values[0], 10);
  213. } else {
  214. return parseInt(values[0], 10);
  215. }
  216. },
  217. showPointerValue: function(pointer, position, animate) {
  218. var label = $('.pointer-label', this.domNode)[pointer.hasClass('low') ? 'first' : 'last']();
  219. var text;
  220. var value = this.positionToValue(position);
  221. if ($.isFunction(this.options.format)) {
  222. var type = this.isSingle() ? undefined : (pointer.hasClass('low') ? 'low' : 'high');
  223. text = this.options.format(value, type);
  224. } else {
  225. text = this.options.format.replace('%s', value);
  226. }
  227. var width = label.html(text).width(),
  228. left = position - width / 2;
  229. left = Math.min(Math.max(left, 0), this.options.width - width);
  230. label[animate ? 'animate' : 'css']({
  231. left: left
  232. });
  233. this.setInputValue(pointer, value);
  234. },
  235. valuesToPrc: function(values) {
  236. var lowPrc = ((values[0] - this.options.from) * 100 / this.interval),
  237. highPrc = ((values[1] - this.options.from) * 100 / this.interval);
  238. return [lowPrc, highPrc];
  239. },
  240. prcToPx: function(prc) {
  241. return (this.domNode.width() * prc) / 100;
  242. },
  243. positionToValue: function(pos) {
  244. var value = (pos / this.domNode.width()) * this.interval;
  245. value = value + this.options.from;
  246. return Math.round(value / this.options.step) * this.options.step;
  247. },
  248. setInputValue: function(pointer, v) {
  249. // if(!isChanged) return;
  250. if (this.isSingle()) {
  251. this.options.value = v.toString();
  252. } else {
  253. var values = this.options.value.split(',');
  254. if (pointer.hasClass('low')) {
  255. this.options.value = v + ',' + values[1];
  256. } else {
  257. this.options.value = values[0] + ',' + v;
  258. }
  259. }
  260. if (this.inputNode.val() !== this.options.value) {
  261. this.inputNode.val(this.options.value);
  262. this.options.onstatechange.call(this, this.options.value);
  263. }
  264. },
  265. getValue: function() {
  266. return this.options.value;
  267. },
  268. isReadonly: function(){
  269. this.domNode.toggleClass('slider-readonly', this.options.disable);
  270. },
  271. disable: function(){
  272. this.options.disable = true;
  273. this.isReadonly();
  274. },
  275. enable: function(){
  276. this.options.disable = false;
  277. this.isReadonly();
  278. },
  279. toggleDisable: function(){
  280. this.options.disable = !this.options.disable;
  281. this.isReadonly();
  282. }
  283. };
  284. /*$.jRange = function (node, options) {
  285. var jNode = $(node);
  286. if(!jNode.data('jrange')){
  287. jNode.data('jrange', new jRange(node, options));
  288. }
  289. return jNode.data('jrange');
  290. };
  291. $.fn.jRange = function (options) {
  292. return this.each(function(){
  293. $.jRange(this, options);
  294. });
  295. };*/
  296. var pluginName = 'jRange';
  297. // A really lightweight plugin wrapper around the constructor,
  298. // preventing against multiple instantiations
  299. $.fn[pluginName] = function(option) {
  300. var args = arguments,
  301. result;
  302. this.each(function() {
  303. var $this = $(this),
  304. data = $.data(this, 'plugin_' + pluginName),
  305. options = typeof option === 'object' && option;
  306. if (!data) {
  307. $this.data('plugin_' + pluginName, (data = new jRange(this, options)));
  308. $(window).resize(function() {
  309. data.setValue(data.getValue());
  310. }); // Update slider position when window is resized to keep it in sync with scale
  311. }
  312. // if first argument is a string, call silimarly named function
  313. // this gives flexibility to call functions of the plugin e.g.
  314. // - $('.dial').plugin('destroy');
  315. // - $('.dial').plugin('render', $('.new-child'));
  316. if (typeof option === 'string') {
  317. result = data[option].apply(data, Array.prototype.slice.call(args, 1));
  318. }
  319. });
  320. // To enable plugin returns values
  321. return result || this;
  322. };
  323. })(jQuery, window, document);