336 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*jshint multistr:true, curly: false */
 | |
| /*global jQuery:false, define: false */
 | |
| /**
 | |
|  * jRange - Awesome range control
 | |
|  *
 | |
|  * Written by
 | |
|  * ----------
 | |
|  * Nitin Hayaran (nitinhayaran@gmail.com)
 | |
|  *
 | |
|  * Licensed under the MIT (MIT-LICENSE.txt).
 | |
|  *
 | |
|  * @author Nitin Hayaran
 | |
|  * @version 0.1-RELEASE
 | |
|  *
 | |
|  * Dependencies
 | |
|  * ------------
 | |
|  * jQuery (http://jquery.com)
 | |
|  *
 | |
|  **/
 | |
| ;
 | |
| (function($, window, document, undefined) {
 | |
| 	'use strict';
 | |
| 
 | |
| 	var jRange = function() {
 | |
| 		return this.init.apply(this, arguments);
 | |
| 	};
 | |
| 	jRange.prototype = {
 | |
| 		defaults: {
 | |
| 			onstatechange: function() {},
 | |
| 			isRange: false,
 | |
| 			showLabels: true,
 | |
| 			showScale: true,
 | |
| 			step: 1,
 | |
| 			format: '%s',
 | |
| 			theme: 'theme-green',
 | |
| 			width: 300,
 | |
| 			disable: false
 | |
| 		},
 | |
| 		template: '<div class="slider-container">\
 | |
| 			<div class="back-bar">\
 | |
|                 <div class="selected-bar"></div>\
 | |
|                 <div class="pointer low"></div><div class="pointer-label">123456</div>\
 | |
|                 <div class="pointer high"></div><div class="pointer-label">456789</div>\
 | |
|                 <div class="clickable-dummy"></div>\
 | |
|             </div>\
 | |
|             <div class="scale"></div>\
 | |
| 		</div>',
 | |
| 		init: function(node, options) {
 | |
| 			this.options       = $.extend({}, this.defaults, options);
 | |
| 			this.inputNode     = $(node);
 | |
| 			this.options.value = this.inputNode.val() || (this.options.isRange ? this.options.from + ',' + this.options.from : this.options.from);
 | |
| 			this.domNode       = $(this.template);
 | |
| 			this.domNode.addClass(this.options.theme);
 | |
| 			this.inputNode.after(this.domNode);
 | |
| 			this.domNode.on('change', this.onChange);
 | |
| 			this.pointers      = $('.pointer', this.domNode);
 | |
| 			this.lowPointer    = this.pointers.first();
 | |
| 			this.highPointer   = this.pointers.last();
 | |
| 			this.labels        = $('.pointer-label', this.domNode);
 | |
| 			this.lowLabel      = this.labels.first();
 | |
| 			this.highLabel     = this.labels.last();
 | |
| 			this.scale         = $('.scale', this.domNode);
 | |
| 			this.bar           = $('.selected-bar', this.domNode);
 | |
| 			this.clickableBar  = this.domNode.find('.clickable-dummy');
 | |
| 			this.interval      = this.options.to - this.options.from;
 | |
| 			this.render();
 | |
| 		},
 | |
| 		render: function() {
 | |
| 			// Check if inputNode is visible, and have some width, so that we can set slider width accordingly.
 | |
| 			if (this.inputNode.width() === 0 && !this.options.width) {
 | |
| 				console.log('jRange : no width found, returning');
 | |
| 				return;
 | |
| 			} else {
 | |
| 				this.domNode.width(this.options.width || this.inputNode.width());
 | |
| 				this.inputNode.hide();
 | |
| 			}
 | |
| 
 | |
| 			if (this.isSingle()) {
 | |
| 				this.lowPointer.hide();
 | |
| 				this.lowLabel.hide();
 | |
| 			}
 | |
| 			if (!this.options.showLabels) {
 | |
| 				this.labels.hide();
 | |
| 			}
 | |
| 			this.attachEvents();
 | |
| 			if (this.options.showScale) {
 | |
| 				this.renderScale();
 | |
| 			}
 | |
| 			this.setValue(this.options.value);
 | |
| 		},
 | |
| 		isSingle: function() {
 | |
| 			if (typeof(this.options.value) === 'number') {
 | |
| 				return true;
 | |
| 			}
 | |
| 			return (this.options.value.indexOf(',') !== -1 || this.options.isRange) ?
 | |
| 				false : true;
 | |
| 		},
 | |
| 		attachEvents: function() {
 | |
| 			this.clickableBar.click($.proxy(this.barClicked, this));
 | |
| 			this.pointers.on('mousedown touchstart', $.proxy(this.onDragStart, this));
 | |
| 			this.pointers.bind('dragstart', function(event) {
 | |
| 				event.preventDefault();
 | |
| 			});
 | |
| 		},
 | |
| 		onDragStart: function(e) {
 | |
| 			if ( this.options.disable || (e.type === 'mousedown' && e.which !== 1)) {
 | |
| 				return;
 | |
| 			}
 | |
| 			e.stopPropagation();
 | |
| 			e.preventDefault();
 | |
| 			var pointer = $(e.target);
 | |
| 			this.pointers.removeClass('last-active');
 | |
| 			pointer.addClass('focused last-active');
 | |
| 			this[(pointer.hasClass('low') ? 'low' : 'high') + 'Label'].addClass('focused');
 | |
| 			$(document).on('mousemove.slider touchmove.slider', $.proxy(this.onDrag, this, pointer));
 | |
| 			$(document).on('mouseup.slider touchend.slider touchcancel.slider', $.proxy(this.onDragEnd, this));
 | |
| 		},
 | |
| 		onDrag: function(pointer, e) {
 | |
| 			e.stopPropagation();
 | |
| 			e.preventDefault();
 | |
| 
 | |
| 			if (e.originalEvent.touches && e.originalEvent.touches.length) {
 | |
| 				e = e.originalEvent.touches[0];
 | |
| 			} else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
 | |
| 				e = e.originalEvent.changedTouches[0];
 | |
| 			}
 | |
| 
 | |
| 			var position = e.clientX - this.domNode.offset().left;
 | |
| 			this.domNode.trigger('change', [this, pointer, position]);
 | |
| 		},
 | |
| 		onDragEnd: function(e) {
 | |
| 			this.pointers.removeClass('focused');
 | |
| 			this.labels.removeClass('focused');
 | |
| 			$(document).off('.slider');
 | |
| 		},
 | |
| 		barClicked: function(e) {
 | |
| 			if(this.options.disable) return;
 | |
| 			var x = e.pageX - this.clickableBar.offset().left;
 | |
| 			if (this.isSingle())
 | |
| 				this.setPosition(this.pointers.last(), x, true, true);
 | |
| 			else {
 | |
| 				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) ?
 | |
| 					this.pointers.first() : this.pointers.last();
 | |
| 				this.setPosition(pointer, x, true, true);
 | |
| 			}
 | |
| 		},
 | |
| 		onChange: function(e, self, pointer, position) {
 | |
| 			var min, max;
 | |
| 			if (self.isSingle()) {
 | |
| 				min = 0;
 | |
| 				max = self.domNode.width();
 | |
| 			} else {
 | |
| 				min = pointer.hasClass('high') ? self.lowPointer.position().left + self.lowPointer.width() / 2 : 0;
 | |
| 				max = pointer.hasClass('low') ? self.highPointer.position().left + self.highPointer.width() / 2 : self.domNode.width();
 | |
| 			}
 | |
| 			var value = Math.min(Math.max(position, min), max);
 | |
| 			self.setPosition(pointer, value, true);
 | |
| 		},
 | |
| 		setPosition: function(pointer, position, isPx, animate) {
 | |
| 			var leftPos,
 | |
| 				lowPos = this.lowPointer.position().left,
 | |
| 				highPos = this.highPointer.position().left,
 | |
| 				circleWidth = this.highPointer.width() / 2;
 | |
| 			if (!isPx) {
 | |
| 				position = this.prcToPx(position);
 | |
| 			}
 | |
| 			if (pointer[0] === this.highPointer[0]) {
 | |
| 				highPos = Math.round(position - circleWidth);
 | |
| 			} else {
 | |
| 				lowPos = Math.round(position - circleWidth);
 | |
| 			}
 | |
| 			pointer[animate ? 'animate' : 'css']({
 | |
| 				'left': Math.round(position - circleWidth)
 | |
| 			});
 | |
| 			if (this.isSingle()) {
 | |
| 				leftPos = 0;
 | |
| 			} else {
 | |
| 				leftPos = lowPos + circleWidth;
 | |
| 			}
 | |
| 			this.bar[animate ? 'animate' : 'css']({
 | |
| 				'width': Math.round(highPos + circleWidth - leftPos),
 | |
| 				'left': leftPos
 | |
| 			});
 | |
| 			this.showPointerValue(pointer, position, animate);
 | |
| 			this.isReadonly();
 | |
| 		},
 | |
| 		// will be called from outside
 | |
| 		setValue: function(value) {
 | |
| 			var values = value.toString().split(',');
 | |
| 			this.options.value = value;
 | |
| 			var prc = this.valuesToPrc(values.length === 2 ? values : [0, values[0]]);
 | |
| 			if (this.isSingle()) {
 | |
| 				this.setPosition(this.highPointer, prc[1]);
 | |
| 			} else {
 | |
| 				this.setPosition(this.lowPointer, prc[0]);
 | |
| 				this.setPosition(this.highPointer, prc[1]);
 | |
| 			}
 | |
| 		},
 | |
| 		renderScale: function() {
 | |
| 			var s = this.options.scale || [this.options.from, this.options.to];
 | |
| 			var prc = Math.round((100 / (s.length - 1)) * 10) / 10;
 | |
| 			var str = '';
 | |
| 			for (var i = 0; i < s.length; i++) {
 | |
| 				str += '<span style="left: ' + i * prc + '%">' + (s[i] != '|' ? '<ins>' + s[i] + '</ins>' : '') + '</span>';
 | |
| 			}
 | |
| 			this.scale.html(str);
 | |
| 
 | |
| 			$('ins', this.scale).each(function() {
 | |
| 				$(this).css({
 | |
| 					marginLeft: -$(this).outerWidth() / 2
 | |
| 				});
 | |
| 			});
 | |
| 		},
 | |
| 		getBarWidth: function() {
 | |
| 			var values = this.options.value.split(',');
 | |
| 			if (values.length > 1) {
 | |
| 				return parseInt(values[1], 10) - parseInt(values[0], 10);
 | |
| 			} else {
 | |
| 				return parseInt(values[0], 10);
 | |
| 			}
 | |
| 		},
 | |
| 		showPointerValue: function(pointer, position, animate) {
 | |
| 			var label = $('.pointer-label', this.domNode)[pointer.hasClass('low') ? 'first' : 'last']();
 | |
| 			var text;
 | |
| 			var value = this.positionToValue(position);
 | |
| 			if ($.isFunction(this.options.format)) {
 | |
| 				var type = this.isSingle() ? undefined : (pointer.hasClass('low') ? 'low' : 'high');
 | |
| 				text = this.options.format(value, type);
 | |
| 			} else {
 | |
| 				text = this.options.format.replace('%s', value);
 | |
| 			}
 | |
| 
 | |
| 			var width = label.html(text).width(),
 | |
| 				left = position - width / 2;
 | |
| 			left = Math.min(Math.max(left, 0), this.options.width - width);
 | |
| 			label[animate ? 'animate' : 'css']({
 | |
| 				left: left
 | |
| 			});
 | |
| 			this.setInputValue(pointer, value);
 | |
| 		},
 | |
| 		valuesToPrc: function(values) {
 | |
| 			var lowPrc = ((values[0] - this.options.from) * 100 / this.interval),
 | |
| 				highPrc = ((values[1] - this.options.from) * 100 / this.interval);
 | |
| 			return [lowPrc, highPrc];
 | |
| 		},
 | |
| 		prcToPx: function(prc) {
 | |
| 			return (this.domNode.width() * prc) / 100;
 | |
| 		},
 | |
| 		positionToValue: function(pos) {
 | |
| 			var value = (pos / this.domNode.width()) * this.interval;
 | |
| 			value = value + this.options.from;
 | |
| 			return Math.round(value / this.options.step) * this.options.step;
 | |
| 		},
 | |
| 		setInputValue: function(pointer, v) {
 | |
| 			// if(!isChanged) return;
 | |
| 			if (this.isSingle()) {
 | |
| 				this.options.value = v.toString();
 | |
| 			} else {
 | |
| 				var values = this.options.value.split(',');
 | |
| 				if (pointer.hasClass('low')) {
 | |
| 					this.options.value = v + ',' + values[1];
 | |
| 				} else {
 | |
| 					this.options.value = values[0] + ',' + v;
 | |
| 				}
 | |
| 			}
 | |
| 			if (this.inputNode.val() !== this.options.value) {
 | |
| 				this.inputNode.val(this.options.value);
 | |
| 				this.options.onstatechange.call(this, this.options.value);
 | |
| 			}
 | |
| 		},
 | |
| 		getValue: function() {
 | |
| 			return this.options.value;
 | |
| 		},
 | |
| 		isReadonly: function(){
 | |
| 			this.domNode.toggleClass('slider-readonly', this.options.disable);
 | |
| 		},
 | |
| 		disable: function(){
 | |
| 			this.options.disable = true;
 | |
| 			this.isReadonly();
 | |
| 		},
 | |
| 		enable: function(){
 | |
| 			this.options.disable = false;
 | |
| 			this.isReadonly();
 | |
| 		},
 | |
| 		toggleDisable: function(){
 | |
| 			this.options.disable = !this.options.disable;
 | |
| 			this.isReadonly();
 | |
| 		}
 | |
| 	};
 | |
| 
 | |
| 	/*$.jRange = function (node, options) {
 | |
| 		var jNode = $(node);
 | |
| 		if(!jNode.data('jrange')){
 | |
| 			jNode.data('jrange', new jRange(node, options));
 | |
| 		}
 | |
| 		return jNode.data('jrange');
 | |
| 	};
 | |
| 
 | |
| 	$.fn.jRange = function (options) {
 | |
| 		return this.each(function(){
 | |
| 			$.jRange(this, options);
 | |
| 		});
 | |
| 	};*/
 | |
| 
 | |
| 	var pluginName = 'jRange';
 | |
| 	// A really lightweight plugin wrapper around the constructor,
 | |
| 	// preventing against multiple instantiations
 | |
| 	$.fn[pluginName] = function(option) {
 | |
| 		var args = arguments,
 | |
| 			result;
 | |
| 
 | |
| 		this.each(function() {
 | |
| 			var $this = $(this),
 | |
| 				data = $.data(this, 'plugin_' + pluginName),
 | |
| 				options = typeof option === 'object' && option;
 | |
| 			if (!data) {
 | |
| 				$this.data('plugin_' + pluginName, (data = new jRange(this, options)));
 | |
| 				$(window).resize(function() {
 | |
| 					data.setValue(data.getValue());
 | |
| 				}); // Update slider position when window is resized to keep it in sync with scale
 | |
| 			}
 | |
| 			// if first argument is a string, call silimarly named function
 | |
| 			// this gives flexibility to call functions of the plugin e.g.
 | |
| 			//   - $('.dial').plugin('destroy');
 | |
| 			//   - $('.dial').plugin('render', $('.new-child'));
 | |
| 			if (typeof option === 'string') {
 | |
| 				result = data[option].apply(data, Array.prototype.slice.call(args, 1));
 | |
| 			}
 | |
| 		});
 | |
| 
 | |
| 		// To enable plugin returns values
 | |
| 		return result || this;
 | |
| 	};
 | |
| 
 | |
| })(jQuery, window, document);
 |