// PIPS
(function($) {
"use strict";
var extensionMethods = {
pips: function( settings ) {
var slider = this, i, j, p, collection = "",
mousedownHandlers,
min = slider._valueMin(),
max = slider._valueMax(),
value = slider._value(),
values = slider._values(),
pips = ( max - min ) / slider.options.step,
$handles = slider.element.find(".ui-slider-handle"),
$pips;
var options = {
first: "label",
// "label", "pip", false
last: "label",
// "label", "pip", false
rest: "pip",
// "label", "pip", false
labels: false,
// [array], { first: "string", rest: [array], last: "string" }, false
prefix: "",
// "", string
suffix: "",
// "", string
step: ( pips > 100 ) ? Math.floor( pips * 0.05 ) : 1,
// number
formatLabel: function(value) {
return this.prefix + value + this.suffix;
}
// function
// must return a value to display in the pip labels
};
if ( $.type( settings ) === "object" || $.type( settings ) === "undefined" ) {
$.extend( options, settings );
} else {
if ( settings === "destroy" ) {
destroy();
}
return;
}
// we don't want the step ever to be a floating point.
slider.options.pipStep = Math.round( options.step );
// get rid of all pips that might already exist.
slider.element
.off( ".selectPip" )
.addClass("ui-slider-pips")
.find(".ui-slider-pip")
.remove();
// small object with functions for marking pips as selected.
var selectPip = {
single: function(value) {
this.resetClasses();
$pips
.filter(".ui-slider-pip-" + this.classLabel(value) )
.addClass("ui-slider-pip-selected");
if ( slider.options.range ) {
$pips.each(function(k,v) {
var pipVal = $(v).children(".ui-slider-label").data("value");
if (( slider.options.range === "min" && pipVal < value ) ||
( slider.options.range === "max" && pipVal > value )) {
$(v).addClass("ui-slider-pip-inrange");
}
});
}
},
range: function(values) {
this.resetClasses();
for( i = 0; i < values.length; i++ ) {
$pips
.filter(".ui-slider-pip-" + this.classLabel(values[i]) )
.addClass("ui-slider-pip-selected-" + (i+1) );
}
if ( slider.options.range ) {
$pips.each(function(k,v) {
var pipVal = $(v).children(".ui-slider-label").data("value");
if( pipVal > values[0] && pipVal < values[1] ) {
$(v).addClass("ui-slider-pip-inrange");
}
});
}
},
classLabel: function(value) {
return value.toString().replace(".","-");
},
resetClasses: function() {
var regex = /(^|\s*)(ui-slider-pip-selected|ui-slider-pip-inrange)(-{1,2}\d+|\s|$)/gi;
$pips.removeClass( function (index, css) {
return ( css.match(regex) || [] ).join(" ");
});
}
};
function getClosestHandle( val ) {
var h, k,
sliderVals,
comparedVals,
closestVal,
tempHandles = [],
closestHandle = 0;
if( values && values.length ) {
// get the current values of the slider handles
sliderVals = slider.element.slider("values");
// find the offset value from the `val` for each
// handle, and store it in a new array
comparedVals = $.map( sliderVals, function(v) {
return Math.abs( v - val );
});
// figure out the closest handles to the value
closestVal = Math.min.apply( Math, comparedVals );
// If a comparedVal is the closestVal, then
// set the value accordingly, and set the closest handle.
for( h = 0; h < comparedVals.length; h++ ) {
if( comparedVals[h] === closestVal ) {
tempHandles.push(h);
}
}
// set the closest handle to the first handle in array,
// just incase we have no _lastChangedValue to compare to.
closestHandle = tempHandles[0];
// now we want to find out if any of the closest handles were
// the last changed handle, if so we specify that handle to change
for( k = 0; k < tempHandles.length; k++ ) {
if( slider._lastChangedValue === tempHandles[k] ) {
closestHandle = tempHandles[k];
}
}
if ( slider.options.range && tempHandles.length === 2 ) {
if ( val > sliderVals[1] ) {
closestHandle = tempHandles[1];
} else if ( val < sliderVals[0] ) {
closestHandle = tempHandles[0];
}
}
}
return closestHandle;
}
function destroy() {
slider.element
.off(".selectPip")
.on("mousedown.slider", slider.element.data("mousedown-original") )
.removeClass("ui-slider-pips")
.find(".ui-slider-pip")
.remove();
}
// when we click on a label, we want to make sure the
// slider's handle actually goes to that label!
// so we check all the handles and see which one is closest
// to the label we clicked. If 2 handles are equidistant then
// we move both of them. We also want to trigger focus on the
// handle.
// without this method the label is just treated like a part
// of the slider and there's no accuracy in the selected value
function labelClick( label ) {
if (slider.option("disabled")) {
return;
}
var val = $(label).data("value"),
indexToChange = getClosestHandle( val );
if ( values && values.length ) {
slider.element.slider("values", indexToChange, val );
} else {
slider.element.slider("value", val );
}
slider._lastChangedValue = indexToChange;
}
// method for creating a pip. We loop this for creating all
// the pips.
function createPip( which ) {
var label,
percent,
number = which,
classes = "ui-slider-pip",
css = "";
if ( "first" === which ) { number = 0; }
else if ( "last" === which ) { number = pips; }
// labelValue is the actual value of the pip based on the min/step
var labelValue = min + ( slider.options.step * number );
// classLabel replaces any decimals with hyphens
var classLabel = labelValue.toString().replace(".","-");
// We need to set the human-readable label to either the
// corresponding element in the array, or the appropriate
// item in the object... or an empty string.
if( $.type(options.labels) === "array" ) {
label = options.labels[number] || "";
}
else if( $.type( options.labels ) === "object" ) {
// set first label
if( "first" === which ) {
label = options.labels.first || "";
}
// set last label
else if( "last" === which ) {
label = options.labels.last || "";
}
// set other labels, but our index should start at -1
// because of the first pip.
else if( $.type( options.labels.rest ) === "array" ) {
label = options.labels.rest[ number - 1 ] || "";
}
// urrggh, the options must be f**ked, just show nothing.
else {
label = labelValue;
}
}
else {
label = labelValue;
}
// First Pip on the Slider
if ( "first" === which ) {
percent = "0%";
classes += " ui-slider-pip-first";
classes += ( "label" === options.first ) ? " ui-slider-pip-label" : "";
classes += ( false === options.first ) ? " ui-slider-pip-hide" : "";
// Last Pip on the Slider
} else if ( "last" === which ) {
percent = "100%";
classes += " ui-slider-pip-last";
classes += ( "label" === options.last ) ? " ui-slider-pip-label" : "";
classes += ( false === options.last ) ? " ui-slider-pip-hide" : "";
// All other Pips
} else {
percent = ((100/pips) * which).toFixed(4) + "%";
classes += ( "label" === options.rest ) ? " ui-slider-pip-label" : "";
classes += ( false === options.rest ) ? " ui-slider-pip-hide" : "";
}
classes += " ui-slider-pip-" + classLabel;
// add classes for the initial-selected values.
if ( values && values.length ) {
for( i = 0; i < values.length; i++ ) {
if ( labelValue === values[i] ) {
classes += " ui-slider-pip-initial-" + (i+1);
classes += " ui-slider-pip-selected-" + (i+1);
}
}
if ( slider.options.range ) {
if( labelValue > values[0] &&
labelValue < values[1] ) {
classes += " ui-slider-pip-inrange";
}
}
} else {
if ( labelValue === value ) {
classes += " ui-slider-pip-initial";
classes += " ui-slider-pip-selected";
}
if ( slider.options.range ) {
if (( slider.options.range === "min" && labelValue < value ) ||
( slider.options.range === "max" && labelValue > value )) {
classes += " ui-slider-pip-inrange";
}
}
}
css = ( slider.options.orientation === "horizontal" ) ?
"left: "+ percent :
"bottom: "+ percent;
// add this current pip to the collection
return ""+
""+
""+ options.formatLabel(label) +""+
"";
}
// create our first pip
collection += createPip("first");
// for every stop in the slider; we create a pip.
for( p = 1; p < pips; p++ ) {
if( p % slider.options.pipStep === 0 ) {
collection += createPip( p );
}
}
// create our last pip
collection += createPip("last");
// append the collection of pips.
slider.element.append( collection );
// store the pips for setting classes later.
$pips = slider.element.find(".ui-slider-pip");
// store the mousedown handlers for later, just in case we reset
// the slider, the handler would be lost!
if ( $._data( slider.element.get(0), "events").mousedown &&
$._data( slider.element.get(0), "events").mousedown.length ) {
mousedownHandlers = $._data( slider.element.get(0), "events").mousedown;
} else {
mousedownHandlers = slider.element.data("mousedown-handlers");
}
slider.element.data("mousedown-handlers", mousedownHandlers.slice() );
// loop through all the mousedown handlers on the slider,
// and store the original namespaced (.slider) event handler so
// we can trigger it later.
for( j = 0; j < mousedownHandlers.length; j++ ) {
if( mousedownHandlers[j].namespace === "slider" ) {
slider.element.data("mousedown-original", mousedownHandlers[j].handler );
}
}
// unbind the mousedown.slider event, because it interferes with
// the labelClick() method (stops smooth animation), and decide
// if we want to trigger the original event based on which element
// was clicked.
slider.element
.off("mousedown.slider")
.on("mousedown.selectPip", function(e) {
var $target = $(e.target),
closest = getClosestHandle( $target.data("value") ),
$handle = $handles.eq( closest );
$handle.addClass("ui-state-active");
if( $target.is(".ui-slider-label") ) {
labelClick( $target );
slider.element
.one("mouseup.selectPip", function() {
$handle
.removeClass("ui-state-active")
.focus();
});
} else {
var originalMousedown = slider.element.data("mousedown-original");
originalMousedown(e);
}
});
slider.element.on( "slide.selectPip slidechange.selectPip", function(e,ui) {
var $slider = $(this),
value = $slider.slider("value"),
values = $slider.slider("values");
if ( ui ) {
value = ui.value;
values = ui.values;
}
if ( values && values.length ) {
selectPip.range( values );
} else {
selectPip.single( value );
}
});
},
// FLOATS
float: function( settings ) {
var i,
slider = this,
min = slider._valueMin(),
max = slider._valueMax(),
value = slider._value(),
values = slider._values(),
tipValues = [],
$handles = slider.element.find(".ui-slider-handle");
var options = {
handle: true,
// false
pips: false,
// true
labels: false,
// [array], { first: "string", rest: [array], last: "string" }, false
prefix: "",
// "", string
suffix: "",
// "", string
event: "slidechange slide",
// "slidechange", "slide", "slidechange slide"
formatLabel: function(value) {
return this.prefix + value + this.suffix;
}
// function
// must return a value to display in the floats
};
if ( $.type( settings ) === "object" || $.type( settings ) === "undefined" ) {
$.extend( options, settings );
} else {
if ( settings === "destroy" ) {
destroy();
}
return;
}
if ( value < min ) {
value = min;
}
if ( value > max ) {
value = max;
}
if ( values && values.length ) {
for( i = 0; i < values.length; i++ ) {
if ( values[i] < min ) {
values[i] = min;
}
if ( values[i] > max ) {
values[i] = max;
}
}
}
// add a class for the CSS
slider.element
.addClass("ui-slider-float")
.find(".ui-slider-tip, .ui-slider-tip-label")
.remove();
function destroy() {
slider.element
.off(".sliderFloat")
.removeClass("ui-slider-float")
.find(".ui-slider-tip, .ui-slider-tip-label")
.remove();
}
function getPipLabels( values ) {
// when checking the array we need to divide
// by the step option, so we store those values here.
var vals = [],
steppedVals = $.map( values, function(v) {
return Math.ceil(( v - min ) / slider.options.step);
});
// now we just get the values we need to return
// by looping through the values array and assigning the
// label if it exists.
if( $.type( options.labels ) === "array" ) {
for( i = 0; i < values.length; i++ ) {
vals[i] = options.labels[ steppedVals[i] ] || values[i];
}
}
else if( $.type( options.labels ) === "object" ) {
for( i = 0; i < values.length; i++ ) {
if( values[i] === min ) {
vals[i] = options.labels.first || min;
}
else if( values[i] === max ) {
vals[i] = options.labels.last || max;
}
else if( $.type( options.labels.rest ) === "array" ) {
vals[i] = options.labels.rest[ steppedVals[i] - 1 ] || values[i];
}
else {
vals[i] = values[i];
}
}
}
else {
for( i = 0; i < values.length; i++ ) {
vals[i] = values[i];
}
}
return vals;
}
// apply handle tip if settings allows.
if ( options.handle ) {
// We need to set the human-readable label to either the
// corresponding element in the array, or the appropriate
// item in the object... or an empty string.
tipValues = ( values && values.length ) ?
getPipLabels( values ) :
getPipLabels( [ value ] );
for( i = 0; i < tipValues.length; i++ ) {
$handles
.eq( i )
.append( $(""+ options.formatLabel(tipValues[i]) +"") );
}
}
if ( options.pips ) {
// if this slider also has pip-labels, we make those into tips, too.
slider.element.find(".ui-slider-label").each(function(k,v) {
var $this = $(v),
val = [ $this.data("value") ],
label,
$tip;
label = options.formatLabel( getPipLabels( val )[0] );
// create a tip element
$tip =
$("" + label + "")
.insertAfter( $this );
});
}
// check that the event option is actually valid against our
// own list of the slider's events.
if ( options.event !== "slide" &&
options.event !== "slidechange" &&
options.event !== "slide slidechange" &&
options.event !== "slidechange slide" ) {
options.event = "slidechange slide";
}
// when slider changes, update handle tip label.
slider.element
.off(".sliderFloat")
.on( options.event + ".sliderFloat", function( e, ui ) {
var uiValue = ( $.type( ui.value ) === "array" ) ? ui.value : [ ui.value ],
val = options.formatLabel( getPipLabels( uiValue )[0] );
$(ui.handle)
.find(".ui-slider-tip")
.html( val );
});
}
};
$.extend(true, $.ui.slider.prototype, extensionMethods);
})(jQuery);