' ).appendTo( target );
// Throw an error if the slider was already initialized.
if ( target.data( 'base' ) ) {
throw new Error( 'Slider was already initialized.' );
}
// Apply classes and data to the target.
target.data( 'base', base ).addClass( [clsList[6], clsList[16 + options['direction']], clsList[10 + options['orientation']]].join( ' ' ) );
for ( i = 0; i < options['handles']; i ++ ) {
handle = $( '
' ).appendTo( base );
// Add all default and option-specific classes to the
// origins and handles.
handle.addClass( clsList[1] );
handle.children().addClass( [clsList[2], clsList[2] + clsList[7 + options['direction'] + (options['direction'] ? - 1 * i : i)]].join( ' ' ) );
// Make sure every handle has access to all variables.
handle.data( {
'base': base, 'target': target, 'options': options, 'grab': handle.children(), 'pct': - 1
} ).attr( 'data-style', options['style'] );
// Every handle has a storage point, which takes care
// of triggering the proper serialization callbacks.
handle.data( {
'store': store( handle, i, options['serialization'] )
} );
// Store handles on the base
handles.push( handle );
}
// Apply the required connection classes to the elements
// that need them. Some classes are made up for several
// segments listed in the class list, to allow easy
// renaming and provide a minor compression benefit.
switch ( options['connect'] ) {
case 1:
target.addClass( clsList[9] );
handles[0].addClass( clsList[12] );
break;
case 3:
handles[1].addClass( clsList[12] );
/* falls through */
case 2:
handles[0].addClass( clsList[9] );
/* falls through */
case 0:
target.addClass( clsList[12] );
break;
}
// Merge base classes with default,
// and store relevant data on the base element.
base.addClass( clsList[0] ).data( {
'target': target, 'options': options, 'handles': handles
} );
// Use the public value method to set the start values.
target.val( options['start'] );
// Attach the standard drag event to the handles.
if ( ! options['behaviour']['fixed'] ) {
for ( i = 0; i < handles.length; i ++ ) {
// These events are only bound to the visual handle
// element, not the 'real' origin element.
attach( actions.start, handles[i].children(), start, {
base: base, target: target, handles: [handles[i]]
} );
}
}
// Attach the tap event to the slider base.
if ( options['behaviour']['tap'] ) {
attach( actions.start, base, tap, {
base: base, target: target
} );
}
// Extend tapping behaviour to target
if ( options['behaviour']['extend'] ) {
target.addClass( clsList[19] );
if ( options['behaviour']['tap'] ) {
attach( actions.start, target, edge, {
base: base, target: target
} );
}
}
// Make the range dragable.
if ( options['behaviour']['drag'] ) {
dragable = base.find( '.' + clsList[9] ).addClass( clsList[18] );
// When the range is fixed, the entire range can
// be dragged by the handles. The handle in the first
// origin will propagate the start event upward,
// but it needs to be bound manually on the other.
if ( options['behaviour']['fixed'] ) {
dragable = dragable.add( base.children().not( dragable ).data( 'grab' ) );
}
attach( actions.start, dragable, start, {
base: base, target: target, handles: handles
} );
}
} );
}
// Return value for the slider, relative to 'range'.
function getValue() {
/*jshint validthis: true */
var base = $( this ).data( 'base' ), answer = [];
// Loop the handles, and get the value from the input
// for every handle on its' own.
$.each( base.data( 'handles' ), function() {
answer.push( $( this ).data( 'store' ).val() );
} );
// If the slider has just one handle, return a single value.
// Otherwise, return an array, which is in reverse order
// if the slider is used RTL.
if ( answer.length === 1 ) {
return answer[0];
}
if ( base.data( 'options' ).direction ) {
return answer.reverse();
}
return answer;
}
// Set value for the slider, relative to 'range'.
function setValue( args, set ) {
/*jshint validthis: true */
// If the value is to be set to a number, which is valid
// when using a one-handle slider, wrap it in an array.
if ( ! Array.isArray( args ) ) {
args = [args];
}
// Setting is handled properly for each slider in the data set.
return this.each( function() {
var b = $( this ).data( 'base' ), to, i,
handles = Array.prototype.slice.call( b.data( 'handles' ), 0 ), settings = b.data( 'options' );
// If there are multiple handles to be set run the setting
// mechanism twice for the first handle, to make sure it
// can be bounced of the second one properly.
if ( handles.length > 1 ) {
handles[2] = handles[0];
}
// The RTL settings is implemented by reversing the front-end,
// internal mechanisms are the same.
if ( settings['direction'] ) {
args.reverse();
}
for ( i = 0; i < handles.length; i ++ ) {
// Calculate a new position for the handle.
to = args[i % 2];
// The set request might want to ignore this handle.
// Test for 'undefined' too, as a two-handle slider
// can still be set with an integer.
if ( to === null || to === undefined ) {
continue;
}
// Add support for the comma (,) as a decimal symbol.
// Replace it by a period so it is handled properly by
// parseFloat. Omitting this would result in a removal
// of decimals. This way, the developer can also
// input a comma separated string.
if ( typeof to === 'string' ) {
to = to.replace( ',', '.' );
}
// Calculate the new handle position
to = toPercentage( settings['range'], parseFloat( to ) );
// Invert the value if this is an right-to-left slider.
if ( settings['direction'] ) {
to = 100 - to;
}
// If the value of the input doesn't match the slider,
// reset it. Sometimes the input is changed to a value the
// slider has rejected. This can occur when using 'select'
// or 'input[type="number"]' elements. In this case, set
// the value back to the input.
if ( setHandle( handles[i], to ) !== true ) {
handles[i].data( 'store' ).val( true );
}
// Optionally trigger the 'set' event.
if ( set === true ) {
call( settings['set'], $( this ) );
}
}
} );
}
// Unbind all attached events, remove classed and HTML.
function destroy( target ) {
// Start the list of elements to be unbound with the target.
var elements = [[target, '']];
// Get the fields bound to both handles.
$.each( target.data( 'base' ).data( 'handles' ), function() {
elements = elements.concat( $( this ).data( 'store' ).elements );
} );
// Remove all events added by noUiSlider.
$.each( elements, function() {
if ( this.length > 1 ) {
this[0].off( namespace );
}
} );
// Remove all classes from the target.
target.removeClass( clsList.join( ' ' ) );
// Empty the target and remove all data.
target.empty().removeData( 'base options' );
}
// Merge options with current initialization, destroy slider
// and reinitialize.
function build( options ) {
/*jshint validthis: true */
return this.each( function() {
// When uninitialised, jQuery will return '',
// Zepto returns undefined. Both are falsy.
var values = $( this ).val() || false,
current = $( this ).data( 'options' ), // Extend the current setup with the new options.
setup = $.extend( {}, current, options );
// If there was a slider initialised, remove it first.
if ( values !== false ) {
destroy( $( this ) );
}
// Make the destroy method publicly accessible.
if ( ! options ) {
return;
}
// Create a new slider
$( this )[noConflict]( setup );
// Set the slider values back. If the start options changed,
// it gets precedence.
if ( values !== false && setup.start === current.start ) {
$( this ).val( values );
}
} );
}
// Overwrite the native jQuery value function
// with a simple handler. noUiSlider will use the internal
// value method, anything else will use the standard method.
$.fn.val = function() {
// If the function is called without arguments,
// act as a 'getter'. Call the getValue function
// in the same scope as this call.
if ( this.hasClass( clsList[6] ) ) {
return arguments.length ? setValue.apply( this, arguments ) : getValue.apply( this );
}
// If this isn't noUiSlider, continue with jQuery's
// original method.
return $VAL.apply( this, arguments );
};
return (rebuild ? build : create).call( this, options );
};
}( window['jQuery'] || window['Zepto'] ));