jquery.range.js 11 KB

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