/** * jquery.slider - Slider ui control in jQuery * * Written by * Egor Khmelev (hmelyoff@gmail.com) * * Licensed under the MIT (MIT-LICENSE.txt). * * @author Egor Khmelev * @version 1.1.0-RELEASE ($Id$) * * Dependencies * * jQuery (http://jquery.com) * jquery.numberformatter (http://code.google.com/p/jquery-numberformatter/) * tmpl (http://ejohn.org/blog/javascript-micro-templating/) * jquery.dependClass * draggable * **/ (function( $ ) { function isArray( value ){ if( typeof value == "undefined" ) return false; if (value instanceof Array || (!(value instanceof Object) && (Object.prototype.toString.call((value)) == '[object Array]') || typeof value.length == 'number' && typeof value.splice != 'undefined' && typeof value.propertyIsEnumerable != 'undefined' && !value.propertyIsEnumerable('splice') )) { return true; } return false; } $.slider = function( node, settings ){ var jNode = $(node); if( !jNode.data( "jslider" ) ) jNode.data( "jslider", new jSlider( node, settings ) ); return jNode.data( "jslider" ); }; $.fn.slider = function( action, opt_value ){ var returnValue, args = arguments; function isDef( val ){ return val !== undefined; }; function isDefAndNotNull( val ){ return val != null; }; this.each(function(){ var self = $.slider( this, action ); // do actions if( typeof action == "string" ){ switch( action ){ case "value": if( isDef( args[ 1 ] ) && isDef( args[ 2 ] ) ){ var pointers = self.getPointers(); if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){ pointers[0].set( args[ 1 ] ); pointers[0].setIndexOver(); } if( isDefAndNotNull( pointers[1] ) && isDefAndNotNull( args[2] ) ){ pointers[1].set( args[ 2 ] ); pointers[1].setIndexOver(); } } else if( isDef( args[ 1 ] ) ){ var pointers = self.getPointers(); if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){ pointers[0].set( args[ 1 ] ); pointers[0].setIndexOver(); } } else returnValue = self.getValue(); break; case "prc": if( isDef( args[ 1 ] ) && isDef( args[ 2 ] ) ){ var pointers = self.getPointers(); if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){ pointers[0]._set( args[ 1 ] ); pointers[0].setIndexOver(); } if( isDefAndNotNull( pointers[1] ) && isDefAndNotNull( args[2] ) ){ pointers[1]._set( args[ 2 ] ); pointers[1].setIndexOver(); } } else if( isDef( args[ 1 ] ) ){ var pointers = self.getPointers(); if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){ pointers[0]._set( args[ 1 ] ); pointers[0].setIndexOver(); } } else returnValue = self.getPrcValue(); break; case "calculatedValue": var value = self.getValue().split(";"); returnValue = ""; for (var i=0; i < value.length; i++) { returnValue += (i > 0 ? ";" : "") + self.nice( value[i] ); }; break; case "skin": self.setSkin( args[1] ); break; }; } // return actual object else if( !action && !opt_value ){ if( !isArray( returnValue ) ) returnValue = []; returnValue.push( self ); } }); // flatten array just with one slider if( isArray( returnValue ) && returnValue.length == 1 ) returnValue = returnValue[ 0 ]; return returnValue || this; }; var OPTIONS = { settings: { from: 1, to: 10, step: 1, smooth: true, limits: true, round: 0, format: { format: "#,##0.##" }, value: "5;7", dimension: "" }, className: "jslider", selector: ".jslider-", template: tmpl( '' + '
' + '
' + '' + '' + '
' + '
' + '
' + '
<%=settings.from%>
' + '
<%=settings.to%><%=settings.dimension%>
' + '
<%=settings.dimension%>
' + '
<%=settings.dimension%>
' + '
<%=scale%>
'+ '
' + '
' ) }; function jSlider(){ return this.init.apply( this, arguments ); }; jSlider.prototype.init = function( node, settings ){ this.settings = $.extend(true, {}, OPTIONS.settings, settings ? settings : {}); // obj.sliderHandler = this; this.inputNode = $( node ).hide(); this.settings.interval = this.settings.to-this.settings.from; this.settings.value = this.inputNode.attr("value"); if( this.settings.calculate && $.isFunction( this.settings.calculate ) ) this.nice = this.settings.calculate; if( this.settings.onstatechange && $.isFunction( this.settings.onstatechange ) ) this.onstatechange = this.settings.onstatechange; this.is = { init: false }; this.o = {}; this.create(); }; jSlider.prototype.onstatechange = function(){ }; jSlider.prototype.create = function(){ var $this = this; this.domNode = $( OPTIONS.template({ className: OPTIONS.className, settings: { from: this.nice( this.settings.from ), to: this.nice( this.settings.to ), dimension: this.settings.dimension }, scale: this.generateScale() }) ); this.inputNode.after( this.domNode ); this.drawScale(); // set skin class if( this.settings.skin && this.settings.skin.length > 0 ) this.setSkin( this.settings.skin ); this.sizes = { domWidth: this.domNode.width(), domOffset: this.domNode.offset() }; // find some objects $.extend(this.o, { pointers: {}, labels: { 0: { o: this.domNode.find(OPTIONS.selector + "value").not(OPTIONS.selector + "value-to") }, 1: { o: this.domNode.find(OPTIONS.selector + "value").filter(OPTIONS.selector + "value-to") } }, limits: { 0: this.domNode.find(OPTIONS.selector + "label").not(OPTIONS.selector + "label-to"), 1: this.domNode.find(OPTIONS.selector + "label").filter(OPTIONS.selector + "label-to") } }); $.extend(this.o.labels[0], { value: this.o.labels[0].o.find("span") }); $.extend(this.o.labels[1], { value: this.o.labels[1].o.find("span") }); if( !$this.settings.value.split(";")[1] ){ this.settings.single = true; this.domNode.addDependClass("single"); } if( !$this.settings.limits ) this.domNode.addDependClass("limitless"); this.domNode.find(OPTIONS.selector + "pointer").each(function( i ){ var value = $this.settings.value.split(";")[i]; if( value ){ $this.o.pointers[i] = new jSliderPointer( this, i, $this ); var prev = $this.settings.value.split(";")[i-1]; if( prev && new Number(value) < new Number(prev) ) value = prev; value = value < $this.settings.from ? $this.settings.from : value; value = value > $this.settings.to ? $this.settings.to : value; $this.o.pointers[i].set( value, true ); } }); this.o.value = this.domNode.find(".v"); this.is.init = true; $.each(this.o.pointers, function(i){ $this.redraw(this); }); (function(self){ $(window).resize(function(){ self.onresize(); }); })(this); }; jSlider.prototype.setSkin = function( skin ){ if( this.skin_ ) this.domNode.removeDependClass( this.skin_, "_" ); this.domNode.addDependClass( this.skin_ = skin, "_" ); }; jSlider.prototype.setPointersIndex = function( i ){ $.each(this.getPointers(), function(i){ this.index( i ); }); }; jSlider.prototype.getPointers = function(){ return this.o.pointers; }; jSlider.prototype.generateScale = function(){ if( this.settings.scale && this.settings.scale.length > 0 ){ var str = ""; var s = this.settings.scale; var prc = Math.round((100/(s.length-1))*10)/10; for( var i=0; i < s.length; i++ ){ str += '' + ( s[i] != '|' ? '' + s[i] + '' : '' ) + ''; }; return str; } else return ""; return ""; }; jSlider.prototype.drawScale = function(){ this.domNode.find(OPTIONS.selector + "scale span ins").each(function(){ $(this).css({ marginLeft: -$(this).outerWidth()/2 }); }); }; jSlider.prototype.onresize = function(){ var self = this; this.sizes = { domWidth: this.domNode.width(), domOffset: this.domNode.offset() }; $.each(this.o.pointers, function(i){ self.redraw(this); }); }; jSlider.prototype.update = function(){ this.onresize(); this.drawScale(); }; jSlider.prototype.limits = function( x, pointer ){ // smooth if( !this.settings.smooth ){ var step = this.settings.step*100 / ( this.settings.interval ); x = Math.round( x/step ) * step; } var another = this.o.pointers[1-pointer.uid]; if( another && pointer.uid && x < another.value.prc ) x = another.value.prc; if( another && !pointer.uid && x > another.value.prc ) x = another.value.prc; // base limit if( x < 0 ) x = 0; if( x > 100 ) x = 100; return Math.round( x*10 ) / 10; }; jSlider.prototype.redraw = function( pointer ){ if( !this.is.init ) return false; this.setValue(); // redraw range line if( this.o.pointers[0] && this.o.pointers[1] ) this.o.value.css({ left: this.o.pointers[0].value.prc + "%", width: ( this.o.pointers[1].value.prc - this.o.pointers[0].value.prc ) + "%" }); this.o.labels[pointer.uid].value.html( this.nice( pointer.value.origin ) ); // redraw position of labels this.redrawLabels( pointer ); }; jSlider.prototype.redrawLabels = function( pointer ){ function setPosition( label, sizes, prc ){ sizes.margin = -sizes.label/2; // left limit label_left = sizes.border + sizes.margin; if( label_left < 0 ) sizes.margin -= label_left; // right limit if( sizes.border+sizes.label / 2 > self.sizes.domWidth ){ sizes.margin = 0; sizes.right = true; } else sizes.right = false; label.o.css({ left: prc + "%", marginLeft: sizes.margin, right: "auto" }); if( sizes.right ) label.o.css({ left: "auto", right: 0 }); return sizes; } var self = this; var label = this.o.labels[pointer.uid]; var prc = pointer.value.prc; var sizes = { label: label.o.outerWidth(), right: false, border: ( prc * this.sizes.domWidth ) / 100 }; if( !this.settings.single ){ // glue if near; var another = this.o.pointers[1-pointer.uid]; var another_label = this.o.labels[another.uid]; switch( pointer.uid ){ case 0: if( sizes.border+sizes.label / 2 > another_label.o.offset().left-this.sizes.domOffset.left ){ another_label.o.css({ visibility: "hidden" }); another_label.value.html( this.nice( another.value.origin ) ); label.o.css({ visibility: "visible" }); prc = ( another.value.prc - prc ) / 2 + prc; if( another.value.prc != pointer.value.prc ){ label.value.html( this.nice(pointer.value.origin) + " – " + this.nice(another.value.origin) ); sizes.label = label.o.outerWidth(); sizes.border = ( prc * this.sizes.domWidth ) / 100; } } else { another_label.o.css({ visibility: "visible" }); } break; case 1: if( sizes.border - sizes.label / 2 < another_label.o.offset().left - this.sizes.domOffset.left + another_label.o.outerWidth() ){ another_label.o.css({ visibility: "hidden" }); another_label.value.html( this.nice(another.value.origin) ); label.o.css({ visibility: "visible" }); prc = ( prc - another.value.prc ) / 2 + another.value.prc; if( another.value.prc != pointer.value.prc ){ label.value.html( this.nice(another.value.origin) + " – " + this.nice(pointer.value.origin) ); sizes.label = label.o.outerWidth(); sizes.border = ( prc * this.sizes.domWidth ) / 100; } } else { another_label.o.css({ visibility: "visible" }); } break; } } sizes = setPosition( label, sizes, prc ); /* draw second label */ if( another_label ){ var sizes = { label: another_label.o.outerWidth(), right: false, border: ( another.value.prc * this.sizes.domWidth ) / 100 }; sizes = setPosition( another_label, sizes, another.value.prc ); } this.redrawLimits(); }; jSlider.prototype.redrawLimits = function(){ if( this.settings.limits ){ var limits = [ true, true ]; for( key in this.o.pointers ){ if( !this.settings.single || key == 0 ){ var pointer = this.o.pointers[key]; var label = this.o.labels[pointer.uid]; var label_left = label.o.offset().left - this.sizes.domOffset.left; var limit = this.o.limits[0]; if( label_left < limit.outerWidth() ) limits[0] = false; var limit = this.o.limits[1]; if( label_left + label.o.outerWidth() > this.sizes.domWidth - limit.outerWidth() ) limits[1] = false; } }; for( var i=0; i < limits.length; i++ ){ if( limits[i] ) this.o.limits[i].fadeIn("fast"); else this.o.limits[i].fadeOut("fast"); }; } }; jSlider.prototype.setValue = function(){ var value = this.getValue(); this.inputNode.attr( "value", value ); this.onstatechange.call( this, value ); }; jSlider.prototype.getValue = function(){ if(!this.is.init) return false; var $this = this; var value = ""; $.each( this.o.pointers, function(i){ if( this.value.prc != undefined && !isNaN(this.value.prc) ) value += (i > 0 ? ";" : "") + $this.prcToValue( this.value.prc ); }); return value; }; jSlider.prototype.getPrcValue = function(){ if(!this.is.init) return false; var $this = this; var value = ""; $.each( this.o.pointers, function(i){ if( this.value.prc != undefined && !isNaN(this.value.prc) ) value += (i > 0 ? ";" : "") + this.value.prc; }); return value; }; jSlider.prototype.prcToValue = function( prc ){ if( this.settings.heterogeneity && this.settings.heterogeneity.length > 0 ){ var h = this.settings.heterogeneity; var _start = 0; var _from = this.settings.from; for( var i=0; i <= h.length; i++ ){ if( h[i] ) var v = h[i].split("/"); else var v = [100, this.settings.to]; v[0] = new Number(v[0]); v[1] = new Number(v[1]); if( prc >= _start && prc <= v[0] ) { var value = _from + ( (prc-_start) * (v[1]-_from) ) / (v[0]-_start); } _start = v[0]; _from = v[1]; }; } else { var value = this.settings.from + ( prc * this.settings.interval ) / 100; } return this.round( value ); }; jSlider.prototype.valueToPrc = function( value, pointer ){ if( this.settings.heterogeneity && this.settings.heterogeneity.length > 0 ){ var h = this.settings.heterogeneity; var _start = 0; var _from = this.settings.from; for (var i=0; i <= h.length; i++) { if(h[i]) var v = h[i].split("/"); else var v = [100, this.settings.to]; v[0] = new Number(v[0]); v[1] = new Number(v[1]); if(value >= _from && value <= v[1]){ var prc = pointer.limits(_start + (value-_from)*(v[0]-_start)/(v[1]-_from)); } _start = v[0]; _from = v[1]; }; } else { var prc = pointer.limits((value-this.settings.from)*100/this.settings.interval); } return prc; }; jSlider.prototype.round = function( value ){ value = Math.round( value / this.settings.step ) * this.settings.step; if( this.settings.round ) value = Math.round( value * Math.pow(10, this.settings.round) ) / Math.pow(10, this.settings.round); else value = Math.round( value ); return value; }; jSlider.prototype.nice = function( value ){ value = value.toString().replace(/,/gi, ".").replace(/ /gi, "");; if( $.formatNumber ){ return $.formatNumber( new Number(value), this.settings.format || {} ).replace( /-/gi, "−" ); } else { return new Number(value); } }; function jSliderPointer(){ Draggable.apply( this, arguments ); } jSliderPointer.prototype = new Draggable(); jSliderPointer.prototype.oninit = function( ptr, id, _constructor ){ this.uid = id; this.parent = _constructor; this.value = {}; this.settings = this.parent.settings; }; jSliderPointer.prototype.onmousedown = function(evt){ this._parent = { offset: this.parent.domNode.offset(), width: this.parent.domNode.width() }; this.ptr.addDependClass("hover"); this.setIndexOver(); }; jSliderPointer.prototype.onmousemove = function( evt, x ){ var coords = this._getPageCoords( evt ); this._set( this.calc( coords.x ) ); }; jSliderPointer.prototype.onmouseup = function( evt ){ if( this.parent.settings.callback && $.isFunction(this.parent.settings.callback) ) this.parent.settings.callback.call( this.parent, this.parent.getValue() ); this.ptr.removeDependClass("hover"); }; jSliderPointer.prototype.setIndexOver = function(){ this.parent.setPointersIndex( 1 ); this.index( 2 ); }; jSliderPointer.prototype.index = function( i ){ this.ptr.css({ zIndex: i }); }; jSliderPointer.prototype.limits = function( x ){ return this.parent.limits( x, this ); }; jSliderPointer.prototype.calc = function(coords){ var x = this.limits(((coords-this._parent.offset.left)*100)/this._parent.width); return x; }; jSliderPointer.prototype.set = function( value, opt_origin ){ this.value.origin = this.parent.round(value); this._set( this.parent.valueToPrc( value, this ), opt_origin ); }; jSliderPointer.prototype._set = function( prc, opt_origin ){ if( !opt_origin ) this.value.origin = this.parent.prcToValue(prc); this.value.prc = prc; this.ptr.css({ left: prc + "%" }); this.parent.redraw(this); }; })(jQuery);