nouislider-browser
Advanced tools
Comparing version 4.2.2-1 to 5.0.0-1
@@ -1,123 +0,134 @@ | ||
/** noUiSlider | ||
** @author: Léon Gersen | ||
** @documentation: http://refreshless.com/nouislider/ | ||
**/ | ||
/*jslint browser: true, devel: true, plusplus: true, white: true, unparam: true */ | ||
(function( $, undefined ){ | ||
/*! $.noUiSlider | ||
@version 5.0.0 | ||
@author Leon Gersen https://twitter.com/LeonGersen | ||
@license WTFPL http://www.wtfpl.net/about/ | ||
@documentation http://refreshless.com/nouislider/ | ||
*/ | ||
"use strict"; | ||
// ==ClosureCompiler== | ||
// @externs_url http://refreshless.com/externs/jquery-1.8.js | ||
// @compilation_level ADVANCED_OPTIMIZATIONS | ||
// @warning_level VERBOSE | ||
// ==/ClosureCompiler== | ||
if ( $.zepto && !$.fn.removeData ) { | ||
throw new ReferenceError("Zepto is loaded without the data module."); | ||
/*jshint laxcomma: true */ | ||
/*jshint smarttabs: true */ | ||
/*jshint sub: true */ | ||
/*jslint browser: true */ | ||
/*jslint continue: true */ | ||
/*jslint plusplus: true */ | ||
/*jslint white: true */ | ||
/*jslint sub: true */ | ||
(function( $ ){ | ||
'use strict'; | ||
if ( $['zepto'] && !$.fn.removeData ) { | ||
throw new ReferenceError('Zepto is loaded without the data module.'); | ||
} | ||
$.fn.noUiSlider = function( options ){ | ||
$.fn['noUiSlider'] = function( options, rebuild ){ | ||
var namespace = '.nui' | ||
// Create a shorthand for document event binding. | ||
,all = $(document) | ||
// Create a map of touch and mouse actions. | ||
,actions = { | ||
start: 'mousedown touchstart' | ||
,move: 'mousemove touchmove' | ||
,end: 'mouseup touchend' | ||
} | ||
// Make a copy of the current 'val' function. | ||
,$VAL = $.fn.val | ||
// Define a set of standard HTML classes for | ||
// the various structures noUiSlider uses. | ||
,clsList = [ | ||
'noUi-base' // 0 | ||
,'noUi-origin' // 1 | ||
,'noUi-handle' // 2 | ||
,'noUi-input' // 3 | ||
,'noUi-active' // 4 | ||
,'noUi-state-tap' // 5 | ||
,'noUi-target' // 6 | ||
,'-lower' // 7 | ||
,'-upper' // 8 | ||
,'noUi-connect' // 9 | ||
,'noUi-vertical' // 10 | ||
,'noUi-horizontal' // 11 | ||
,'noUi-background' // 12 | ||
,'noUi-z-index' // 13 | ||
] | ||
// Define an extendible object with base classes for the various | ||
// structure elements in the slider. These can be extended by simply | ||
// pushing to the array, which reduces '.addClass()' calls. | ||
,stdCls = { | ||
base: [clsList[0]] | ||
,origin: [clsList[1]] | ||
,handle: [clsList[2]] | ||
} | ||
// This object contains some well tested functions to convert | ||
// values to and from percentages. It can be a bit strange to wrap | ||
// your head around the individual calls, but they'll do their job | ||
// with all positive and negative input values. | ||
,percentage = { | ||
to: function ( range, value ) { | ||
value = range[0] < 0 ? value + Math.abs(range[0]) : value - range[0]; | ||
return (value * 100) / this.len(range); | ||
} | ||
,from: function ( range, value ) { | ||
return (value * 100) / this.len(range); | ||
} | ||
,is: function ( range, value ) { | ||
return ((value * this.len(range)) / 100) + range[0]; | ||
} | ||
,len: function ( range ) { | ||
return (range[0] > range[1] ? range[0] - range[1] : range[1] - range[0]); | ||
} | ||
} | ||
// Event handlers bound to elements to perform basic tasks. | ||
,eventHandlers = [ | ||
// Assign input field values to the slider, | ||
// and signal unevaluated input. | ||
function ( ) { | ||
var | ||
// Cache the document and body selectors; | ||
doc = $(document) | ||
,body = $('body') | ||
this.target.val([ | ||
!this.i ? this.val() : null | ||
, this.i ? this.val() : null | ||
], { trusted: false }); | ||
// Namespace for binding and unbinding slider events; | ||
,namespace = '.nui' | ||
} | ||
// Shorthand for stopping propagation on an object. | ||
// Calling a function prevents having to define | ||
// one within other code. | ||
,function ( e ) { | ||
e.stopPropagation(); | ||
} | ||
]; | ||
// Copy of the current value function; | ||
,$VAL = $.fn.val | ||
// When the browser supports MsPointerEvents, | ||
// don't bind touch or mouse events. The touch events are | ||
// currently only implemented by IE10, but they are stable | ||
// and convenient to use. IE11 implements pointerEvents without | ||
// Re-usable list of classes; | ||
,clsList = [ | ||
/* 0 */ 'noUi-base' | ||
/* 1 */ ,'noUi-origin' | ||
/* 2 */ ,'noUi-handle' | ||
/* 3 */ ,'noUi-input' | ||
/* 4 */ ,'noUi-active' | ||
/* 5 */ ,'noUi-state-tap' | ||
/* 6 */ ,'noUi-target' | ||
/* 7 */ ,'-lower' | ||
/* 8 */ ,'-upper' | ||
/* 9 */ ,'noUi-connect' | ||
/* 10 */ ,'noUi-horizontal' | ||
/* 11 */ ,'noUi-vertical' | ||
/* 12 */ ,'noUi-background' | ||
/* 13 */ ,'noUi-stacking' | ||
/* 14 */ ,'noUi-block' | ||
/* 15 */ ,'noUi-state-blocked' | ||
/* 16 */ ,'noUi-ltr' | ||
/* 17 */ ,'noUi-rtl' | ||
/* 18 */ ,'noUi-dragable' | ||
/* 19 */ ,'noUi-extended' | ||
/* 20 */ ,'noUi-state-drag' | ||
] | ||
// Determine the events to bind. IE11 implements pointerEvents without | ||
// a prefix, which breaks compatibility with the IE10 implementation. | ||
if( window.navigator.pointerEnabled ) { | ||
actions = { | ||
start: 'pointerdown' | ||
,move: 'pointermove' | ||
,end: 'pointerup' | ||
}; | ||
} else if ( window.navigator.msPointerEnabled ) { | ||
actions = { | ||
start: 'MSPointerDown' | ||
,move: 'MSPointerMove' | ||
,end: 'MSPointerUp' | ||
}; | ||
,actions = window.navigator['pointerEnabled'] ? { | ||
start: 'pointerdown' | ||
,move: 'pointermove' | ||
,end: 'pointerup' | ||
} : window.navigator['msPointerEnabled'] ? { | ||
start: 'MSPointerDown' | ||
,move: 'MSPointerMove' | ||
,end: 'MSPointerUp' | ||
} : { | ||
start: 'mousedown touchstart' | ||
,move: 'mousemove touchmove' | ||
,end: 'mouseup touchend' | ||
}; | ||
// Percentage calculation | ||
// (percentage) How many percent is this value of this range? | ||
function fromPercentage ( range, value ) { | ||
return (value * 100) / ( range[1] - range[0] ); | ||
} | ||
// Test an array of objects, and calls them if they are a function. | ||
function call ( f, scope, args ) { | ||
// (percentage) Where is this value on this range? | ||
function toPercentage ( range, value ) { | ||
return fromPercentage( range, range[0] < 0 ? | ||
value + Math.abs(range[0]) : | ||
value - range[0] ); | ||
} | ||
// (value) How much is this percentage on this range? | ||
function isPercentage ( range, value ) { | ||
return ((value * ( range[1] - range[0] )) / 100) + range[0]; | ||
} | ||
// Type tests | ||
// Test in an object is an instance of jQuery or Zepto. | ||
function isInstance ( a ) { | ||
return a instanceof $ || ( $['zepto'] && $['zepto']['isZ'](a) ); | ||
} | ||
// Checks whether a value is numerical. | ||
function isNumeric ( a ) { | ||
return !isNaN( parseFloat( a ) ) && isFinite( a ); | ||
} | ||
// General helper functions | ||
// Test an array of objects, and calls them if they are a function. | ||
function call ( functions, scope ) { | ||
// Allow the passing of an unwrapped function. | ||
// Leaves other code a more comprehensible. | ||
if( !$.isArray(f) ){ | ||
f = [f]; | ||
if( !$.isArray( functions ) ){ | ||
functions = [ functions ]; | ||
} | ||
$.each(f,function(i,q){ | ||
if (typeof q === "function") { | ||
q.call(scope, args); | ||
$.each( functions, function(){ | ||
if (typeof this === 'function') { | ||
this.call(scope); | ||
} | ||
@@ -127,19 +138,69 @@ }); | ||
function instance ( object ) { | ||
return object instanceof $ || ( $.zepto && $.zepto.isZ ( object ) ); | ||
// Returns a proxy to set a target using the public value method. | ||
function setN ( target, number ) { | ||
return function(){ | ||
// Determine the correct position to set, | ||
// leave the other one unchanged. | ||
var val = [null, null]; | ||
val[ number ] = $(this).val(); | ||
// Trigger the 'set' callback | ||
target.val(val, true); | ||
}; | ||
} | ||
// Round a value to the closest 'to'. | ||
function closest ( value, to ){ | ||
return Math.round(value / to) * to; | ||
} | ||
// Format output value to specified standards. | ||
function format ( value, options ) { | ||
// Round the value to the resolution that was set | ||
// with the serialization options. | ||
value = value.toFixed( options['decimals'] ); | ||
// Rounding away decimals might cause a value of -0 | ||
// when using very small ranges. Remove those cases. | ||
if ( parseFloat(value) === 0 ) { | ||
value = value.replace('-0', '0'); | ||
} | ||
// Apply the proper decimal mark to the value. | ||
return value.replace( '.', options['serialization']['mark'] ); | ||
} | ||
// Determine the handle closest to an event. | ||
function closestHandle ( handles, location, style ) { | ||
if ( handles.length === 1 ) { | ||
return handles[0]; | ||
} | ||
var total = handles[0].offset()[style] + | ||
handles[1].offset()[style]; | ||
return handles[ location < total / 2 ? 0 : 1 ]; | ||
} | ||
// Round away small numbers in floating point implementation. | ||
function digits ( value, round ) { | ||
return parseFloat(value.toFixed(round)); | ||
} | ||
// Event abstraction | ||
// Provide a clean event with standardized offset values. | ||
function fixEvent ( e ) { | ||
// Required (in at the very least Chrome) to prevent | ||
// scrolling and panning while attempting to slide. | ||
// The tap event also depends on this. This doesn't | ||
// seem to prevent panning in Firefox, which is an issue. | ||
// Prevent-default will also stop Chrome from setting a text-cursor. | ||
// Prevent scrolling and panning on touch events, while | ||
// attempting to slide. The tap event also depends on this. | ||
e.preventDefault(); | ||
// Filter the event to register the type, | ||
// which can be touch, mouse or pointer. Since noUiSlider 4 | ||
// so longer binds touch OR mouse, but rather touch AND mouse, | ||
// offset changes need to be made on an event specific basis. | ||
// Filter the event to register the type, which can be | ||
// touch, mouse or pointer. Offset changes need to be | ||
// made on an event specific basis. | ||
var touch = e.type.indexOf('touch') === 0 | ||
@@ -150,4 +211,3 @@ ,mouse = e.type.indexOf('mouse') === 0 | ||
// IE10 implemented pointer events with a prefix, | ||
// so we'll needs to check for those, too. | ||
// IE10 implemented pointer events with a prefix; | ||
if ( e.type.indexOf('MSPointer') === 0 ) { | ||
@@ -164,4 +224,4 @@ pointer = true; | ||
if ( touch ) { | ||
// noUiSlider supports one movement at a time, for now. | ||
// It is therefore safe to select the first 'changedTouch'. | ||
// noUiSlider supports one movement at a time, | ||
// so we can select the first 'changedTouch'. | ||
x = e.changedTouches[0].pageX; | ||
@@ -183,719 +243,880 @@ y = e.changedTouches[0].pageY; | ||
return $.extend( event, { x:x, y:y } ); | ||
return $.extend( event, { | ||
'pointX': x | ||
,'pointY': y | ||
,cursor: mouse | ||
}); | ||
} | ||
// Handler for attaching events trough a proxy | ||
function attach ( events, target, callback, scope, noAbstraction ) { | ||
// Handler for attaching events trough a proxy | ||
function attach ( events, element, callback, pass ) { | ||
var target = pass.target; | ||
// Add the noUiSlider namespace to all events. | ||
events = events.replace( /\s/g, namespace + ' ' ) + namespace; | ||
// The 'noAbstraction' argument can be set to prevent | ||
// event checking, and instead just proxy the event to | ||
// the right namespace. 'noAbstraction' can be level 1 or 2. | ||
if ( noAbstraction ) { | ||
if ( noAbstraction > 1 ){ | ||
scope = $.extend(target, scope); | ||
} | ||
return target.on( events, $.proxy( callback, scope )); | ||
} | ||
// Bind a closure on the target. | ||
return element.on( events, function( e ){ | ||
// Make the callback available in a lower scope | ||
scope.handler = callback; | ||
// jQuery and Zepto handle unset attributes differently. | ||
var disabled = target.attr('disabled'); | ||
disabled = !( disabled === undefined || disabled === null ); | ||
return target.on( events, $.proxy( function( e ){ | ||
// Test if there is anything that should prevent an event | ||
// from being handled, such as a disabled state or an active | ||
// 'tap' transition. Prevent interaction with disabled sliders. | ||
if( this.target.is('[class*="noUi-state-"], [disabled]') ) { | ||
// 'tap' transition. | ||
if( target.hasClass('noUi-state-tap') || disabled ) { | ||
return false; | ||
} | ||
// Call the event handler with the original event as argument. | ||
// The handler won't know it has been passed trough this | ||
// proxy, and it won't have to filter event validity, because | ||
// that was done here. Since the scope can just be 'this', | ||
// there is no need to use .call(). | ||
this.handler( fixEvent ( e ) ); | ||
}, scope )); | ||
// Call the event handler with three arguments: | ||
// - The event; | ||
// - An object with data for the event; | ||
// - The slider options; | ||
// Having the slider options as a function parameter prevents | ||
// getting it in every function, which muddies things up. | ||
callback ( | ||
fixEvent( e ) | ||
,pass | ||
,target.data('base').data('options') | ||
); | ||
}); | ||
} | ||
// Checks whether a variable is numerical. | ||
function isNumeric ( a ) { | ||
return !isNaN( parseFloat( a ) ) && isFinite( a ); | ||
} | ||
// jQuery doesn't have a method to return a CSS value as a percentage. | ||
function getPercentage( a ){ | ||
return parseFloat(this.style[a]); | ||
} | ||
// Serialization and value storage | ||
function test ( o, set ){ | ||
// Store a value on all serialization targets, or get the current value. | ||
function serialize ( a ) { | ||
// Checks whether a variable is a candidate to be a | ||
// valid serialization target. | ||
function ser(r){ | ||
return ( instance ( r ) || typeof r === 'string' || r === false ); | ||
/*jshint validthis: true */ | ||
// Re-scope target for availability within .each; | ||
var target = this.target; | ||
// Get the value for this handle | ||
if ( a === undefined ) { | ||
return this.element.data('value'); | ||
} | ||
// These tests are structured with an item for every option available. | ||
// Every item contains an 'r' flag, which marks a required option, and | ||
// a 't' function, which in turn takes some arguments: | ||
// - the value for the option | ||
// - [optional] a reference to options object | ||
// - [optional] the option name | ||
// The testing function returns false when an error is detected, | ||
// or true when everything is OK. Every test also has an 'init' | ||
// method which appends the parent object to all children. | ||
// Write the value to all serialization objects | ||
// or store a new value on the handle | ||
if ( a === true ) { | ||
a = this.element.data('value'); | ||
} else { | ||
this.element.data('value', a); | ||
} | ||
var TESTS = { | ||
/* Handles. | ||
* Has default, can be 1 or 2. | ||
*/ | ||
"handles": { | ||
r: true | ||
,t: function(q){ | ||
q = parseInt(q, 10); | ||
return ( q === 1 || q === 2 ); | ||
} | ||
// Prevent a serialization call if the value wasn't initialized. | ||
if ( a === undefined ) { | ||
return; | ||
} | ||
// If the provided element was a function, | ||
// call it with the slider as scope. Otherwise, | ||
// simply call the function on the object. | ||
$.each( this.elements, function() { | ||
if ( typeof this === 'function' ) { | ||
this.call(target, a); | ||
} else { | ||
this[0][this[1]](a); | ||
} | ||
/* Range. | ||
* Must be an array of two numerical floats, | ||
* which can't be identical. | ||
*/ | ||
,"range": { | ||
r: true | ||
,t: function(q,o,w){ | ||
if(q.length!==2){ | ||
return false; | ||
} | ||
// Reset the array to floats | ||
q = [parseFloat(q[0]),parseFloat(q[1])]; | ||
// Test if those floats are numerical | ||
if(!isNumeric(q[0])||!isNumeric(q[1])){ | ||
return false; | ||
} | ||
// When this test is run for range, the values can't | ||
// be identical. | ||
if(w==="range" && q[0] === q[1]){ | ||
return false; | ||
} | ||
// The lowest value must really be the lowest value. | ||
if(q[1]<q[0]){ | ||
return false; | ||
} | ||
o[w]=q; | ||
return true; | ||
} | ||
} | ||
/* Start. | ||
* Must be an array of two numerical floats when handles = 2; | ||
* Uses 'range' test. | ||
* When handles = 1, a single float is also allowed. | ||
*/ | ||
,"start": { | ||
r: true | ||
,t: function(q,o,w){ | ||
if(o.handles === 1){ | ||
if($.isArray(q)){ | ||
q=q[0]; | ||
} | ||
q = parseFloat(q); | ||
o.start = [q]; | ||
return isNumeric(q); | ||
} | ||
return this.parent.range.t(q,o,w); | ||
} | ||
} | ||
/* Connect. | ||
* Must be true or false when handles = 2; | ||
* Can use 'lower' and 'upper' when handles = 1. | ||
*/ | ||
,"connect": { | ||
t: function(q,o){ | ||
return ( q === true | ||
|| q === false | ||
|| ( q === 'lower' && o.handles === 1) | ||
|| ( q === 'upper' && o.handles === 1)); | ||
} | ||
} | ||
/* Connect. | ||
* Will default to horizontal, not required. | ||
*/ | ||
,"orientation": { | ||
t: function(q){ | ||
return ( q === "horizontal" || q === "vertical" ); | ||
} | ||
} | ||
/* Margin. | ||
* Must be a float, has a default value. | ||
*/ | ||
,"margin": { | ||
r: true | ||
,t: function(q,o,w){ | ||
q = parseFloat(q); | ||
o[w]=q; | ||
return isNumeric(q); | ||
} | ||
} | ||
/* Serialization. | ||
* Required, but has default. 'resolution' and 'mark' option, | ||
* are allowed to be missing, 'to' isn't. Must be an array | ||
* when using two handles, can be a single value | ||
* when using one handle. 'mark' can only be period (.) or | ||
* comma (,) to make sure the value can be parsed properly. | ||
*/ | ||
,"serialization": { | ||
r: true | ||
,t: function(q,o){ | ||
}); | ||
} | ||
if(!q.resolution){ | ||
o.serialization.resolution = 0.01; | ||
} else { | ||
switch(q.resolution){ | ||
case 1: | ||
case 0.1: | ||
case 0.01: | ||
case 0.001: | ||
case 0.0001: | ||
case 0.00001: | ||
break; | ||
default: | ||
return false; | ||
} | ||
} | ||
// Map serialization to [ element, method ]. Attach events where required. | ||
function storeElement ( handle, item, number ) { | ||
if(!q.mark){ | ||
o.serialization.mark = '.'; | ||
} else { | ||
return ( q.mark === '.' || q.mark === ',' ); | ||
} | ||
// Add a change event to the supplied jQuery objects, | ||
// which triggers the value-setting function on the target. | ||
if ( isInstance( item ) ) { | ||
if(q.to){ | ||
var elements = [], target = handle.data('target'); | ||
if(o.handles === 1){ | ||
// Wrap the value for one handle into an array. | ||
if(!$.isArray(q.to)){ | ||
q.to = [q.to]; | ||
} | ||
// Write back to the options object; | ||
o.serialization.to = q.to; | ||
// Run test for valid serialization target. | ||
return ser(q.to[0]); | ||
} | ||
return (q.to.length === 2 && ser(q.to[0]) && ser(q.to[1])); | ||
// Link the field to the other handle if the | ||
// slider is inverted. | ||
if ( handle.data('options').direction ) { | ||
number = number ? 0 : 1; | ||
} | ||
} | ||
// Loop all items so the change event is properly bound, | ||
// and the items can individually be added to the array. | ||
item.each(function(){ | ||
// If no 'to' option is specified, | ||
// the serialization option is invalid. | ||
return false; | ||
// Bind the change event. | ||
$(this).on('change' + namespace, setN( target, number )); | ||
} | ||
} | ||
/* Slide. | ||
* Not required. Must be a function. | ||
*/ | ||
,"slide": { | ||
t: function(q){ | ||
return typeof q === "function"; | ||
} | ||
} | ||
/* Set. | ||
* Not required. Must be a function. | ||
* Tested using the 'slide' test. | ||
*/ | ||
,"set": { | ||
t: function(q,o){ | ||
return this.parent.slide.t(q,o); | ||
} | ||
} | ||
/* Step. | ||
* Not required. Tested using the 'margin' test. | ||
*/ | ||
,"step": { | ||
t: function(q,o,w){ | ||
return this.parent.margin.t(q,o,w); | ||
} | ||
} | ||
/* [init] | ||
* Not an option test. Calling this method will return the | ||
* parent object with some cross references that allow | ||
* crawling the object in an upward direction, which | ||
* normally isn't possible in JavaScript. | ||
*/ | ||
,"init": function(){ | ||
var obj = this; | ||
$.each(obj,function(i,c){ | ||
c.parent = obj; | ||
}); | ||
delete this.init; | ||
return this; | ||
} | ||
}, | ||
// Store the element with the proper handler. | ||
elements.push([ $(this), 'val' ]); | ||
}); | ||
// Prepare a set of tests, by adding some internal reference | ||
// values not available in native JavaScript object implementation. | ||
a = TESTS.init(); | ||
return elements; | ||
} | ||
// Loop all provided tests; | ||
// 'v' is the option set, 'i' is the index for the current test. | ||
$.each(a, function( i, v ){ | ||
// Append a new input to the noUiSlider base. | ||
// Prevent the change event from flowing upward. | ||
if ( typeof item === 'string' ) { | ||
// If the value is required but not set, | ||
// or if the test fails, throw an error. | ||
if((v.r && (!o[i] && o[i] !== 0)) || ((o[i] || o[i] === 0) && !v.t(o[i],o,i))){ | ||
item = [ $('<input type="hidden" name="'+ item +'">') | ||
.appendTo(handle) | ||
.addClass(clsList[3]) | ||
.change(function ( e ) { | ||
e.stopPropagation(); | ||
}), 'val']; | ||
} | ||
// For debugging purposes it might be very useful to know | ||
// what option caused the trouble. Since throwing an error | ||
// will prevent further script execution, log the error | ||
// first. Test for console, as it might not be available. | ||
if( console && console.log && console.group ){ | ||
console.group( "Invalid noUiSlider initialisation:" ); | ||
console.log( "Option:\t", i ); | ||
console.log( "Value:\t", o[i] ); | ||
console.log( "Slider:\t", set[0] ); | ||
console.groupEnd(); | ||
} | ||
return [item]; | ||
} | ||
throw new RangeError("noUiSlider"); | ||
} | ||
// Access point and abstraction for serialization. | ||
function store ( handle, i, serialization ) { | ||
var elements = []; | ||
// Loops all items in the provided serialization setting, | ||
// add the proper events to them or create new input fields, | ||
// and add them as data to the handle so they can be kept | ||
// in sync with the slider value. | ||
$.each( serialization['to'][i], function( index ){ | ||
elements = elements.concat( | ||
storeElement( handle, serialization['to'][i][index], i ) | ||
); | ||
}); | ||
return { | ||
element: handle | ||
,elements: elements | ||
,target: handle.data('target') | ||
,'val': serialize | ||
}; | ||
} | ||
function closest( value, to ){ | ||
// Round a value to the closest 'to'. | ||
// Used with the 'step' option. | ||
return Math.round(value / to) * to; | ||
} | ||
function format ( value, target ) { | ||
// Handle placement | ||
// Round the value to the resolution that was set | ||
// with the serialization options. | ||
value = value.toFixed( target.data('decimals') ); | ||
// Fire callback on unsuccessful handle movement. | ||
function block ( base, stateless ) { | ||
// Apply the proper decimal mark to the value. | ||
return value.replace( '.', target.data('mark') ); | ||
var target = base.data('target'); | ||
if ( !target.hasClass(clsList[14]) ){ | ||
// The visual effects should not always be applied. | ||
if ( !stateless ) { | ||
target.addClass(clsList[15]); | ||
setTimeout(function(){ | ||
target.removeClass(clsList[15]); | ||
}, 450); | ||
} | ||
target.addClass(clsList[14]); | ||
call( base.data('options').block, target ); | ||
} | ||
} | ||
function setHandle ( handle, to, forgive ) { | ||
// Change inline style and apply proper classes. | ||
function placeHandle ( handle, to ) { | ||
var nui = handle.data('nui').options | ||
// Get the array of handles from the base. | ||
// Will be undefined at initialisation. | ||
,handles = handle.data('nui').base.data('handles') | ||
// Get some settings from the handle; | ||
,style = handle.data('nui').style | ||
,hLimit; | ||
var settings = handle.data('options'); | ||
// Make sure the value can be parsed. | ||
// This will catch any potential NaN, even though | ||
// no internal function calling setHandle should pass | ||
// invalided parameters. | ||
if( !isNumeric(to) ) { | ||
return false; | ||
to = digits(to, 7); | ||
// If the slider can move, remove the class | ||
// indicating the block state. | ||
handle.data('target').removeClass(clsList[14]); | ||
// Set handle to new location | ||
handle.css( settings['style'], to + '%' ).data('pct', to); | ||
// Force proper handle stacking | ||
if ( handle.is(':first-child') ) { | ||
handle.toggleClass(clsList[13], to > 50 ); | ||
} | ||
// Ignore the call if the handle won't move anyway. | ||
if( to === handle[0].gPct(style) ) { | ||
return false; | ||
if ( settings['direction'] ) { | ||
to = 100 - to; | ||
} | ||
// Limit 'to' to 0 - 100 | ||
to = to < 0 ? 0 : to > 100 ? 100 : to; | ||
// Write the value to the serialization object. | ||
handle.data('store').val( | ||
format ( isPercentage( settings['range'], to ), settings ) | ||
); | ||
} | ||
// Handle the step option, or ignore it. | ||
if( nui.step && !forgive ){ | ||
to = closest( to, percentage.from(nui.range, nui.step)); | ||
} | ||
// Test suggested values and apply margin, step. | ||
function setHandle ( handle, to ) { | ||
// Stop handling this call if the handle won't step to a new value. | ||
if( to === handle[0].gPct(style) ) { | ||
var base = handle.data('base'), settings = base.data('options'), | ||
handles = base.data('handles'), lower = 0, upper = 100; | ||
// Catch invalid user input | ||
if ( !isNumeric( to ) ){ | ||
return false; | ||
} | ||
// We're done if this is the only handle, | ||
// if the handle bounce is trusted to the user | ||
// or on initialisation when handles isn't defined yet. | ||
if( handle.siblings('.' + clsList[1]).length && !forgive && handles ){ | ||
// Handle the step option. | ||
if ( settings['step'] ){ | ||
to = closest( to, settings['step'] ); | ||
} | ||
// Otherwise, the handle should bounce, | ||
// and stop at the other handle. | ||
if ( handle.data('nui').number ) { | ||
hLimit = handles[0][0].gPct(style) + nui.margin; | ||
to = to < hLimit ? hLimit : to; | ||
if ( handles.length > 1 ){ | ||
if ( handle[0] !== handles[0][0] ) { | ||
lower = digits(handles[0].data('pct')+settings['margin'],7); | ||
} else { | ||
hLimit = handles[1][0].gPct(style) - nui.margin; | ||
to = to > hLimit ? hLimit : to; | ||
upper = digits(handles[1].data('pct')-settings['margin'],7); | ||
} | ||
} | ||
// Stop handling this call if the handle can't move past another. | ||
if( to === handle[0].gPct(style) ) { | ||
return false; | ||
} | ||
// Limit position to boundaries. When the handles aren't set yet, | ||
// they return -1 as a percentage value. | ||
to = Math.min( Math.max( to, lower ), upper < 0 ? 100 : upper ); | ||
// Stop handling this call if the handle can't move past another. | ||
// Return an array containing the hit limit, so the caller can | ||
// provide feedback. ( block callback ). | ||
if ( to === handle.data('pct') ) { | ||
return [!lower ? false : lower, upper === 100 ? false : upper]; | ||
} | ||
// Fix for the z-index issue where the lower handle gets stuck | ||
// below the upper one. Since this function is called for every | ||
// movement, toggleClass cannot be used. | ||
if( handle.data('nui').number === 0 && to > 95 ){ | ||
handle.addClass(clsList[13]); | ||
} else { | ||
handle.removeClass(clsList[13]); | ||
} | ||
placeHandle ( handle, to ); | ||
return true; | ||
} | ||
// Set handle to new location | ||
handle.css( style , to + '%' ); | ||
// Handles movement by tapping | ||
function jump ( base, handle, to, callbacks ) { | ||
// Write the value to the serialization object. | ||
handle.data('store').val( | ||
format ( percentage.is( nui.range, to ), handle.data('nui').target ) | ||
); | ||
// Flag the slider as it is now in a transitional state. | ||
// Transition takes 300 ms, so re-enable the slider afterwards. | ||
base.addClass(clsList[5]); | ||
setTimeout(function(){ | ||
base.removeClass(clsList[5]); | ||
}, 300); | ||
return true; | ||
// Move the handle to the new position. | ||
setHandle( handle, to ); | ||
// Trigger the 'slide' and 'set' callbacks, | ||
// pass the target so that it is 'this'. | ||
call( callbacks, base.data('target') ); | ||
base.data('target').change(); | ||
} | ||
function store ( handle, S ) { | ||
var i = handle.data('nui').number, scope = { | ||
target: handle.data('nui').target | ||
,options: handle.data('nui').options | ||
,handle: handle | ||
,i: i | ||
}; | ||
// Event handlers | ||
if( instance ( S.to[i] ) ) { | ||
// Handle movement on document for handle and range drag. | ||
function move ( event, Dt, Op ) { | ||
// Add a change event to the supplied jQuery object, which | ||
// will just trigger the 'val' function on the parent. In some | ||
// cases, the change event will not fire on select elements, | ||
// so listen to 'blur' too. | ||
attach ( 'change blur' | ||
,S.to[i] | ||
,eventHandlers[0] | ||
,scope | ||
,2 ); | ||
// Map event movement to a slider percentage. | ||
var handles = Dt.handles, limits, | ||
proposal = event[ Dt.point ] - Dt.start[ Dt.point ]; | ||
// Triggering the 'set' callback should not occur on the 'blur' | ||
// event, so bind it only to 'change'. | ||
attach ( 'change' | ||
,S.to[i] | ||
,scope.options.set | ||
,scope.target | ||
,1 ); | ||
proposal = ( proposal * 100 ) / Dt.size; | ||
return S.to[i]; | ||
} | ||
if ( handles.length === 1 ) { | ||
if ( typeof S.to[i] === "string" ) { | ||
// Run handle placement, receive true for success or an | ||
// array with potential limits. | ||
limits = setHandle( handles[0], Dt.positions[0] + proposal ); | ||
// Append a new object to the noUiSlider base, | ||
// prevent change events flowing upward. | ||
return $('<input type="hidden" name="' + S.to[i] + '">') | ||
.appendTo(handle) | ||
.addClass(clsList[3]) | ||
.change(eventHandlers[1]); | ||
} | ||
if ( limits !== true ) { | ||
if ( S.to[i] === false ) { | ||
// Create an object capable of handling all jQuery calls. | ||
return { | ||
// The value will be stored a data on the handle. | ||
val : function(a) { | ||
// Value function provides a getter and a setter. | ||
// Can't just test for !a, as a might be 0. | ||
// When no argument is provided, return the value. | ||
// Otherwise, set the value. | ||
if ( a === undefined ) { | ||
return this.handleElement.data('nui-val'); | ||
} | ||
this.handleElement.data('nui-val', a); | ||
if ( $.inArray ( handles[0].data('pct'), limits ) >= 0 ){ | ||
block ( Dt.base, !Op['margin'] ); | ||
} | ||
// The object could be mistaken for a jQuery object, | ||
// make sure that doesn't trigger any errors. | ||
,hasClass: function(){ | ||
return false; | ||
} | ||
// The val function needs access to the handle. | ||
,handleElement: handle | ||
}; | ||
} | ||
} | ||
return; | ||
} | ||
function move( event ) { | ||
} else { | ||
// This function is called often, keep it light. | ||
// Dragging the range could be implemented by forcing the | ||
// 'move' event on both handles, but this solution proved | ||
// lagging on slower devices, resulting in range errors. The | ||
// slightly ugly solution below is considerably faster, and | ||
// it can't move the handle out of sync. Bypass the standard | ||
// setting method, as other checks are needed. | ||
var base = this.base | ||
,style = base.data('style') | ||
// Subtract the initial movement from the current event, | ||
// while taking vertical sliders into account. | ||
,proposal = event.x - this.startEvent.x | ||
,baseSize = style === 'left' ? base.width() : base.height(); | ||
var l1, u1, l2, u2; | ||
// This loop prevents a long ternary for the proposal variable. | ||
if( style === 'top' ) { | ||
proposal = event.y - this.startEvent.y; | ||
} | ||
// Round the proposal to the step setting. | ||
if ( Op['step'] ) { | ||
proposal = closest( proposal, Op['step'] ); | ||
} | ||
proposal = this.position + ( ( proposal * 100 ) / baseSize ); | ||
// Determine the new position, store it twice. Once for | ||
// limiting, once for checking whether placement should occur. | ||
l1 = l2 = Dt.positions[0] + proposal; | ||
u1 = u2 = Dt.positions[1] + proposal; | ||
setHandle( this.handle, proposal ); | ||
// Round the values within a sensible range. | ||
if ( l1 < 0 ) { | ||
u1 += -1 * l1; | ||
l1 = 0; | ||
} else if ( u1 > 100 ) { | ||
l1 -= ( u1 - 100 ); | ||
u1 = 100; | ||
} | ||
// Trigger the 'slide' event, pass the target so that it is 'this'. | ||
call( base.data('options').slide | ||
,base.data('target') ); | ||
// Don't perform placement if no handles are to be changed. | ||
// Check if the lowest value is set to zero. | ||
if ( l2 < 0 && !l1 && !handles[0].data('pct') ) { | ||
return; | ||
} | ||
// The highest value is limited to 100%. | ||
if ( u1 === 100 && u2 > 100 && handles[1].data('pct') === 100 ){ | ||
return; | ||
} | ||
placeHandle ( handles[0], l1 ); | ||
placeHandle ( handles[1], u1 ); | ||
} | ||
// Trigger the 'slide' event, if the handle was moved. | ||
call( Op['slide'], Dt.target ); | ||
} | ||
function end ( ) { | ||
// Unbind move events on document, call callbacks. | ||
function end ( event, Dt, Op ) { | ||
var base = this.base | ||
,handle = this.handle; | ||
// The handle is no longer active, so remove the class. | ||
if ( Dt.handles.length === 1 ) { | ||
Dt.handles[0].data('grab').removeClass(clsList[4]); | ||
} | ||
// The handle is no longer active, so remove | ||
// the class. | ||
handle.children().removeClass(clsList[4]); | ||
// Remove cursor styles and text-selection events bound to the body. | ||
if ( event.cursor ) { | ||
body.css('cursor', '').off( namespace ); | ||
} | ||
// Unbind move and end events, to prevent | ||
// them stacking up over and over; | ||
all.off(actions.move); | ||
all.off(actions.end); | ||
// Unbind the move and end events, which are added on 'start'. | ||
doc.off( namespace ); | ||
// Some text-selection events are bound to the body. | ||
$('body').off(namespace); | ||
// Trigger the change event. | ||
base.data('target').change(); | ||
Dt.target.removeClass( clsList[14] +' '+ clsList[20]).change(); | ||
// Trigger the 'end' callback. | ||
call( handle.data('nui').options.set | ||
,base.data('target') ); | ||
call( Op['set'], Dt.target ); | ||
} | ||
function start ( event ) { | ||
// Bind move events on document. | ||
function start ( event, Dt, Op ) { | ||
var handle = this.handle | ||
,position = handle[0].gPct( handle.data('nui').style ); | ||
// Mark the handle as 'active' so it can be styled. | ||
if( Dt.handles.length === 1 ) { | ||
Dt.handles[0].data('grab').addClass(clsList[4]); | ||
} | ||
handle.children().addClass(clsList[4]); | ||
// A drag should never propagate up to the 'tap' event. | ||
event.stopPropagation(); | ||
// Attach the move event handler, while | ||
// passing all relevant information along. | ||
attach ( actions.move, all, move, { | ||
startEvent: event | ||
,position: position | ||
,base: this.base | ||
,target: this.target | ||
,handle: handle | ||
// Attach the move event. | ||
attach ( actions.move, doc, move, { | ||
start: event | ||
,base: Dt.base | ||
,target: Dt.target | ||
,handles: Dt.handles | ||
,positions: [ Dt.handles[0].data('pct') | ||
,Dt.handles[ Dt.handles.length - 1 ].data('pct') ] | ||
,point: Op['orientation'] ? 'pointY' : 'pointX' | ||
,size: Op['orientation'] ? Dt.base.height() : Dt.base.width() | ||
}); | ||
attach ( actions.end, all, end, { | ||
base: this.base | ||
,target: this.target | ||
,handle: handle | ||
// Unbind all movement when the drag ends. | ||
attach ( actions.end, doc, end, { | ||
target: Dt.target | ||
,handles: Dt.handles | ||
}); | ||
// Prevent text selection when dragging the handles. | ||
// This doesn't prevent the browser defaulting to the I like cursor. | ||
$('body').on( | ||
'selectstart' + namespace | ||
,function( ){ return false; } | ||
); | ||
// Text selection isn't an issue on touch devices, | ||
// so adding additional callbacks isn't required. | ||
if ( event.cursor ) { | ||
// Prevent the 'I' cursor and extend the range-drag cursor. | ||
body.css('cursor', $(event.target).css('cursor')); | ||
// Mark the target with a dragging state. | ||
if ( Dt.handles.length > 1 ) { | ||
Dt.target.addClass(clsList[20]); | ||
} | ||
// Prevent text selection when dragging the handles. | ||
body.on('selectstart' + namespace, function( ){ | ||
return false; | ||
}); | ||
} | ||
} | ||
function selfEnd( event ) { | ||
// Move closest handle to tapped location. | ||
function tap ( event, Dt, Op ) { | ||
// Stop propagation so that the tap handler doesn't interfere; | ||
var base = Dt.base, handle, to, point, size; | ||
// The tap event shouldn't propagate up to trigger 'edge'. | ||
event.stopPropagation(); | ||
// Trigger the end handler. Supply the current scope, | ||
// which contains all required information. | ||
end.call( this ); | ||
// Determine the direction of the slider. | ||
if ( Op['orientation'] ) { | ||
point = event['pointY']; | ||
size = base.height(); | ||
} else { | ||
point = event['pointX']; | ||
size = base.width(); | ||
} | ||
// Find the closest handle and calculate the tapped point. | ||
handle = closestHandle( base.data('handles'), point, Op['style'] ); | ||
to = (( point - base.offset()[ Op['style'] ] ) * 100 ) / size; | ||
// The set handle to the new position. | ||
jump( base, handle, to, [ Op['slide'], Op['set'] ]); | ||
} | ||
function tap ( event ) { | ||
// Move handle to edges when target gets tapped. | ||
function edge ( event, Dt, Op ) { | ||
// If the target contains an active handle, don't trigger | ||
// this event. Tapping shouldn't be possible while dragging. | ||
if ( this.base.find('.' + clsList[4]).length ) { | ||
return; | ||
var handles = Dt.base.data('handles'), to, i; | ||
i = Op['orientation'] ? event['pointY'] : event['pointX']; | ||
i = i < Dt.base.offset()[Op['style']]; | ||
to = i ? 0 : 100; | ||
i = i ? 0 : handles.length - 1; | ||
jump ( Dt.base, handles[i], to, [ Op['slide'], Op['set'] ]); | ||
} | ||
// API | ||
// Validate and standardize input. | ||
function test ( input, sliders ){ | ||
/* Every input option is tested and parsed. This'll prevent | ||
endless validation in internal methods. These tests are | ||
structured with an item for every option available. An | ||
option can be marked as required by setting the 'r' flag. | ||
The testing function is provided with three arguments: | ||
- The provided value for the option; | ||
- A reference to the options object; | ||
- The name for the option; | ||
The testing function returns false when an error is detected, | ||
or true when everything is OK. It can also modify the option | ||
object, to make sure all values can be correctly looped elsewhere. */ | ||
function values ( a ) { | ||
if ( a.length !== 2 ){ | ||
return false; | ||
} | ||
// Convert the array to floats | ||
a = [ parseFloat(a[0]), parseFloat(a[1]) ]; | ||
// Test if all values are numerical | ||
if( !isNumeric(a[0]) || !isNumeric(a[1]) ){ | ||
return false; | ||
} | ||
// The lowest value must really be the lowest value. | ||
if( a[1] < a[0] ){ | ||
return false; | ||
} | ||
return a; | ||
} | ||
// Getting variables from the event is not required, but | ||
// shortens other expressions and is far more convenient; | ||
var i, handle, hCenter, base = this.base | ||
,handles = this.handles | ||
,style = base.data('style') | ||
,eventXY = event[style === 'left' ? 'x' : 'y'] | ||
,baseSize = style === 'left' ? base.width() : base.height() | ||
,offset = { | ||
handles: [] | ||
,base: { | ||
left: base.offset().left | ||
,top: base.offset().top | ||
var serialization = { | ||
resolution: function(q,o){ | ||
// Parse the syntactic sugar that is the serialization | ||
// resolution option to a usable integer. | ||
// Checking for a string '1', since the resolution needs | ||
// to be cast to a string to split in on the period. | ||
switch( q ){ | ||
case 1: | ||
case 0.1: | ||
case 0.01: | ||
case 0.001: | ||
case 0.0001: | ||
case 0.00001: | ||
q = q.toString().split('.'); | ||
o['decimals'] = q[0] === '1' ? 0 : q[1].length; | ||
break; | ||
case undefined: | ||
o['decimals'] = 2; | ||
break; | ||
default: | ||
return false; | ||
} | ||
}; | ||
// Loop handles and add data to the offset list. | ||
for (i = 0; i < handles.length; i++ ) { | ||
offset.handles.push({ | ||
left: handles[i].offset().left | ||
,top: handles[i].offset().top | ||
}); | ||
} | ||
return true; | ||
} | ||
,mark: function(q,o,w){ | ||
// Calculate the central point between the handles; | ||
hCenter = handles.length === 1 ? 0 : | ||
(( offset.handles[0][style] + offset.handles[1][style] ) / 2 ); | ||
if ( !q ) { | ||
o[w]['mark'] = '.'; | ||
return true; | ||
} | ||
// If there is just one handle, | ||
// or the lower handles in closest to the event, | ||
// select the first handle. Otherwise, pick the second. | ||
if ( handles.length === 1 || eventXY < hCenter ){ | ||
handle = handles[0]; | ||
} else { | ||
handle = handles[1]; | ||
} | ||
switch( q ){ | ||
case '.': | ||
case ',': | ||
return true; | ||
default: | ||
return false; | ||
} | ||
} | ||
,to: function(q,o,w){ | ||
// Flag the slider as it is now in a transitional state. | ||
// Transition takes 300 ms, so re-enable the slider afterwards. | ||
base.addClass(clsList[5]); | ||
setTimeout(function(){ | ||
base.removeClass(clsList[5]); | ||
}, 300); | ||
// Checks whether a variable is a candidate to be a | ||
// valid serialization target. | ||
function ser(r){ | ||
return isInstance ( r ) || | ||
typeof r === 'string' || | ||
typeof r === 'function' || | ||
r === false || | ||
( isInstance ( r[0] ) && | ||
typeof r[0][r[1]] === 'function' ); | ||
} | ||
// Calculate the new position for the handle and | ||
// trigger the movement. | ||
setHandle( | ||
handle | ||
,(((eventXY - offset.base[style]) * 100) / baseSize) | ||
); | ||
// Flatten the serialization array into a reliable | ||
// set of elements, which can be tested and looped. | ||
function filter ( value ) { | ||
// Trigger the 'slide' and 'set' callbacks, | ||
// pass the target so that it is 'this'. | ||
call( [ handle.data('nui').options.slide | ||
,handle.data('nui').options.set ] | ||
,base.data('target') ); | ||
var items = [[],[]]; | ||
base.data('target').change(); | ||
// If a single value is provided it can be pushed | ||
// immediately. | ||
if ( ser(value) ) { | ||
items[0].push(value); | ||
} else { | ||
} | ||
// Otherwise, determine whether this is an | ||
// array of single elements or sets. | ||
$.each(value, function(i, val) { | ||
function create ( options ) { | ||
// Don't handle an overflow of elements. | ||
if( i > 1 ){ | ||
return; | ||
} | ||
return this.each(function( index, target ){ | ||
// Decide if this is a group or not | ||
if( ser(val) ){ | ||
items[i].push(val); | ||
} else { | ||
items[i] = items[i].concat(val); | ||
} | ||
}); | ||
} | ||
// Target is the wrapper that will receive all external | ||
// scripting interaction. It has no styling and serves no | ||
// other function. | ||
target = $(target); | ||
target.addClass(clsList[6]); | ||
return items; | ||
} | ||
// Base is the internal main 'bar'. | ||
var i, style, decimals, handle | ||
,base = $('<div/>').appendTo(target) | ||
,handles = [] | ||
,cls = { | ||
base: stdCls.base | ||
,origin: [ | ||
stdCls.origin.concat([clsList[1] + clsList[7]]) | ||
,stdCls.origin.concat([clsList[1] + clsList[8]]) | ||
] | ||
,handle: [ | ||
stdCls.handle.concat([clsList[2] + clsList[7]]) | ||
,stdCls.handle.concat([clsList[2] + clsList[8]]) | ||
] | ||
}; | ||
if ( !q ) { | ||
o[w]['to'] = [[],[]]; | ||
} else { | ||
// Set defaults where applicable; | ||
options = $.extend({ | ||
handles: 2 | ||
,margin: 0 | ||
,orientation: "horizontal" | ||
}, options) || {}; | ||
var i, j; | ||
// Set a default for serialization; | ||
if(!options.serialization){ | ||
options.serialization = { | ||
to : [false, false] | ||
,resolution : 0.01 | ||
,mark: '.' | ||
}; | ||
// Flatten the serialization array | ||
q = filter ( q ); | ||
// Reverse the API for RTL sliders. | ||
if ( o['direction'] && q[1].length ) { | ||
q.reverse(); | ||
} | ||
// Test all elements in the flattened array. | ||
for ( i = 0; i < o['handles']; i++ ) { | ||
for ( j = 0; j < q[i].length; j++ ) { | ||
// Return false on invalid input | ||
if( !ser(q[i][j]) ){ | ||
return false; | ||
} | ||
// Remove 'false' elements, since those | ||
// won't be handled anyway. | ||
if( !q[i][j] ){ | ||
q[i].splice(j, 1); | ||
} | ||
} | ||
} | ||
// Write the new values back | ||
o[w]['to'] = q; | ||
} | ||
return true; | ||
} | ||
}, tests = { | ||
/* Handles. | ||
* Has default, can be 1 or 2. | ||
*/ | ||
'handles': { | ||
'r': true | ||
,'t': function(q){ | ||
q = parseInt(q, 10); | ||
return ( q === 1 || q === 2 ); | ||
} | ||
} | ||
/* Range. | ||
* Must be an array of two numerical floats, | ||
* which can't be identical. | ||
*/ | ||
,'range': { | ||
'r': true | ||
,'t': function(q,o,w){ | ||
// Run all options through a testing mechanism to ensure correct | ||
// input. The test function will throw errors, so there is | ||
// no need to capture the result of this call. It should be noted | ||
// that options might get modified to be handled properly. E.g. | ||
// wrapping integers in arrays. | ||
test(options, target); | ||
o[w] = values(q); | ||
// I can't type serialization any more, and it doesn't compress | ||
// very well, so shorten it. | ||
options.S = options.serialization; | ||
// The values can't be identical. | ||
return o[w] && o[w][0] !== o[w][1]; | ||
} | ||
} | ||
/* Start. | ||
* Must be an array of two numerical floats when handles = 2; | ||
* Uses 'range' test. | ||
* When handles = 1, a single float is also allowed. | ||
*/ | ||
,'start': { | ||
'r': true | ||
,'t': function(q,o,w){ | ||
if( o['handles'] === 1 ){ | ||
if( $.isArray(q) ){ | ||
q = q[0]; | ||
} | ||
q = parseFloat(q); | ||
o.start = [q]; | ||
return isNumeric(q); | ||
} | ||
// 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. | ||
if( options.connect ) { | ||
o[w] = values(q); | ||
return !!o[w]; | ||
} | ||
} | ||
/* Connect. | ||
* Must be true or false when handles = 2; | ||
* Can use 'lower' and 'upper' when handles = 1. | ||
*/ | ||
,'connect': { | ||
'r': true | ||
,'t': function(q,o,w){ | ||
if( options.connect === "lower" ){ | ||
// Add some styling classes to the base; | ||
cls.base.push(clsList[9], clsList[9] + clsList[7]); | ||
// When using the option 'Lower', there is only one | ||
// handle, and thus only one origin. | ||
cls.origin[0].push(clsList[12]); | ||
} else { | ||
cls.base.push(clsList[9] + clsList[8], clsList[12]); | ||
cls.origin[0].push(clsList[9]); | ||
if ( q === 'lower' ) { | ||
o[w] = 1; | ||
} else if ( q === 'upper' ) { | ||
o[w] = 2; | ||
} else if ( q === true ) { | ||
o[w] = 3; | ||
} else if ( q === false ) { | ||
o[w] = 0; | ||
} else { | ||
return false; | ||
} | ||
return true; | ||
} | ||
} | ||
/* Connect. | ||
* Will default to horizontal, not required. | ||
*/ | ||
,'orientation': { | ||
't': function(q,o,w){ | ||
switch (q){ | ||
case 'horizontal': | ||
o[w] = 0; | ||
break; | ||
case 'vertical': | ||
o[w] = 1; | ||
break; | ||
default: return false; | ||
} | ||
return true; | ||
} | ||
} | ||
/* Margin. | ||
* Must be a float, has a default value. | ||
*/ | ||
,'margin': { | ||
'r': true | ||
,'t': function(q,o,w){ | ||
q = parseFloat(q); | ||
o[w] = fromPercentage(o['range'], q); | ||
return isNumeric(q); | ||
} | ||
} | ||
/* Direction. | ||
* Required, can be 'ltr' or 'rtl'. | ||
*/ | ||
,'direction': { | ||
'r': true | ||
,'t': function(q,o,w){ | ||
} else { | ||
cls.base.push(clsList[12]); | ||
switch ( q ) { | ||
case 'ltr': o[w] = 0; | ||
break; | ||
case 'rtl': o[w] = 1; | ||
// Invert connection for RTL sliders; | ||
o['connect'] = [0,2,1,3][o['connect']]; | ||
break; | ||
default: | ||
return false; | ||
} | ||
return true; | ||
} | ||
} | ||
/* Behaviour. | ||
* Required, defines responses to tapping and | ||
* dragging elements. | ||
*/ | ||
,'behaviour': { | ||
'r': true | ||
,'t': function(q,o,w){ | ||
// Parse the syntactic sugar that is the serialization | ||
// resolution option to a usable integer. | ||
style = options.orientation === 'vertical' ? 'top' : 'left'; | ||
o[w] = { | ||
'tap': q !== (q = q.replace('tap', '')) | ||
,'extend': q !== (q = q.replace('extend', '')) | ||
,'drag': q !== (q = q.replace('drag', '')) | ||
,'fixed': q !== (q = q.replace('fixed', '')) | ||
}; | ||
decimals = options.S.resolution.toString().split('.'); | ||
return !q.replace('none','').replace(/\-/g,''); | ||
} | ||
} | ||
/* Serialization. | ||
* Required, but has default. Must be an array | ||
* when using two handles, can be a single value when using | ||
* one handle. 'mark' can be period (.) or comma (,). | ||
*/ | ||
,'serialization': { | ||
'r': true | ||
,'t': function(q,o,w){ | ||
// Checking for a string "1", since the resolution needs | ||
// to be cast to a string to split in on the period. | ||
decimals = decimals[0] === "1" ? 0 : decimals[1].length; | ||
return serialization.to( q['to'], o, w ) && | ||
serialization.resolution( q['resolution'], o ) && | ||
serialization.mark( q['mark'], o, w ); | ||
} | ||
} | ||
/* Slide. | ||
* Not required. Must be a function. | ||
*/ | ||
,'slide': { | ||
't': function(q){ | ||
return $.isFunction(q); | ||
} | ||
} | ||
/* Set. | ||
* Not required. Must be a function. | ||
* Tested using the 'slide' test. | ||
*/ | ||
,'set': { | ||
't': function(q){ | ||
return $.isFunction(q); | ||
} | ||
} | ||
/* Block. | ||
* Not required. Must be a function. | ||
* Tested using the 'slide' test. | ||
*/ | ||
,'block': { | ||
't': function(q){ | ||
return $.isFunction(q); | ||
} | ||
} | ||
/* Step. | ||
* Not required. | ||
*/ | ||
,'step': { | ||
't': function(q,o,w){ | ||
q = parseFloat(q); | ||
o[w] = fromPercentage ( o['range'], q ); | ||
return isNumeric(q); | ||
} | ||
} | ||
}; | ||
// Add classes for horizontal and vertical sliders. | ||
// The horizontal class is provided for completeness, | ||
// as it isn't used in the default theme. | ||
if( options.orientation === "vertical" ){ | ||
cls.base.push(clsList[10]); | ||
} else { | ||
cls.base.push(clsList[11]); | ||
$.each( tests, function( name, test ){ | ||
/*jslint devel: true */ | ||
var value = input[name], isSet = value !== undefined; | ||
// If the value is required but not set, fail. | ||
if( ( test['r'] && !isSet ) || | ||
// If the test returns false, fail. | ||
( isSet && !test['t']( value, input, name ) ) ){ | ||
// For debugging purposes it might be very useful to know | ||
// what option caused the trouble. Since throwing an error | ||
// will prevent further script execution, log the error | ||
// first. Test for console, as it might not be available. | ||
if( console && console.log && console.group ){ | ||
console.group( 'Invalid noUiSlider initialisation:' ); | ||
console.log( 'Option:\t', name ); | ||
console.log( 'Value:\t', value ); | ||
console.log( 'Slider(s):\t', sliders ); | ||
console.groupEnd(); | ||
} | ||
throw new RangeError('noUiSlider'); | ||
} | ||
}); | ||
} | ||
// Merge base classes with default; | ||
base.addClass(cls.base.join(" ")).data('target', target); | ||
// Parse options, add classes, attach events, create HTML. | ||
function create ( options ) { | ||
// Make data accessible in functions throughout the plugin. | ||
target.data({ | ||
base: base | ||
,mark: options.S.mark | ||
,decimals: decimals | ||
}); | ||
/*jshint validthis: true */ | ||
for (i = 0; i < options.handles; i++ ) { | ||
// Store the original set of options on all targets, | ||
// so they can be re-used and re-tested later. | ||
// Make sure to break the relation with the options, | ||
// which will be changed by the 'test' function. | ||
this.data('options', $.extend(true, {}, options)); | ||
// Set defaults where applicable; | ||
options = $.extend({ | ||
'handles': 2 | ||
,'margin': 0 | ||
,'connect': false | ||
,'direction': 'ltr' | ||
,'behaviour': 'tap' | ||
,'orientation': 'horizontal' | ||
}, options); | ||
// Make sure the test for serialization runs. | ||
options['serialization'] = options['serialization'] || {}; | ||
// Run all options through a testing mechanism to ensure correct | ||
// input. The test function will throw errors, so there is | ||
// no need to capture the result of this call. It should be noted | ||
// that options might get modified to be handled properly. E.g. | ||
// wrapping integers in arrays. | ||
test( options, this ); | ||
// Pre-define the styles. | ||
options['style'] = options['orientation'] ? 'top' : 'left'; | ||
return this.each(function(){ | ||
var target = $(this), i, dragable, handles = [], handle, | ||
base = $('<div/>').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 = $('<div><div/></div>').appendTo(base); | ||
@@ -905,99 +1126,146 @@ | ||
// origins and handles. | ||
handle.addClass(cls.origin[i].join(" ")); | ||
handle.children().addClass(cls.handle[i].join(" ")); | ||
handle.addClass( clsList[1] ); | ||
// These events are only bound to the visual handle element, | ||
// not the 'real' origin element. | ||
attach ( actions.start, handle.children(), start, { | ||
base: base | ||
,target: target | ||
,handle: handle | ||
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']) | ||
}); | ||
attach ( actions.end, handle.children(), selfEnd, { | ||
// 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 | ||
,handle: handle | ||
}); | ||
} | ||
// Make sure every handle has access to all primary | ||
// variables. Can't uses jQuery's .data( obj ) structure | ||
// here, as 'store' needs some values from the 'nui' object. | ||
handle.data('nui', { | ||
target: target | ||
,decimals: decimals | ||
,options: options | ||
,base: base | ||
,style: style | ||
,number: i | ||
}).data('store', store ( | ||
handle | ||
,options.S | ||
)); | ||
// Extend tapping behaviour to target | ||
if ( options['behaviour']['extend'] ) { | ||
// Write a function to the native DOM element, since | ||
// jQuery wont let me get the current value in percentages. | ||
handle[0].gPct = getPercentage; | ||
target.addClass( clsList[19] ); | ||
// Make handles loop-able | ||
handles.push(handle); | ||
// Set the handle to its initial position; | ||
setHandle(handle, percentage.to(options.range, options.start[i])); | ||
if ( options['behaviour']['tap'] ) { | ||
attach ( actions.start, target, edge, { | ||
base: base | ||
,target: target | ||
}); | ||
} | ||
} | ||
// The base could use the handles too; | ||
base.data({ | ||
options: options | ||
,handles: handles | ||
,style: style | ||
}); | ||
// Make the range dragable. | ||
if ( options['behaviour']['drag'] ){ | ||
// Add a reference to the handles on the target as well. | ||
target.data({ | ||
handles: handles | ||
}); | ||
dragable = base.find('.'+clsList[9]).addClass(clsList[18]); | ||
// Attach the the tap event to the slider base. | ||
attach ( actions.end, base, tap, { | ||
base: base | ||
,target: target | ||
,handles: handles | ||
}); | ||
// 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 ( ) { | ||
var re = []; | ||
/*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( $(this).data('handles'), function( i, handle ){ | ||
re.push( handle.data('store').val() ); | ||
$.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. | ||
return ( re.length === 1 ? re[0] : re ); | ||
// 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; | ||
} | ||
function val ( args, modifiers ) { | ||
// Set value for the slider, relative to 'range'. | ||
function setValue ( args, set ) { | ||
// If the function is called without arguments, | ||
// act as a 'getter'. Call the getValue function | ||
// in the same scope as this call. | ||
if( args === undefined ){ | ||
return getValue.call( this ); | ||
} | ||
/*jshint validthis: true */ | ||
// Passing the modifiers argument is not required. | ||
// The input might also be 'true', to indicate that the | ||
// 'set' event should be called. | ||
modifiers = modifiers === true ? { trigger: true } : ( modifiers || {} ); | ||
// If the val is to be set to a number, which is valid | ||
// If the value is to be set to a number, which is valid | ||
// when using a one-handle slider, wrap it in an array. | ||
@@ -1009,45 +1277,39 @@ if( !$.isArray(args) ){ | ||
// Setting is handled properly for each slider in the data set. | ||
// Note that the val method is called on the target, which can | ||
// therefore be used in the function. | ||
return this.each(function( index, target ){ | ||
return this.each(function(){ | ||
// Make sure 'target' is a jQuery element. | ||
target = $(target); | ||
var b = $(this).data('base'), to, i, | ||
handles = Array.prototype.slice.call(b.data('handles'),0), | ||
settings = b.data('options'); | ||
$.each( $(this).data('handles'), function( j, handle ){ | ||
// 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( args[j] === null || args[j] === undefined ) { | ||
return; | ||
if( to === null || to === undefined ) { | ||
continue; | ||
} | ||
// Calculate a new position for the handle. | ||
var value, current | ||
,range = handle.data('nui').options.range | ||
,to = args[j], result; | ||
// Assume the input can be trusted. | ||
modifiers.trusted = true; | ||
// Handle user facing input correction. The value is | ||
// 'trusted' when a developer provides it from the 'val' | ||
// method, not when it comes from an input element. | ||
if ( modifiers.trusted === false || args.length === 1 ) { | ||
modifiers.trusted = false; | ||
} | ||
// If one handle isn't set, the other can't move past it. | ||
if ( args.length === 2 && $.inArray( null, args ) >= 0 ) { | ||
modifiers.trusted = false; | ||
} | ||
// 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 is relevant on trusted input too, | ||
// as a developer might input a comma separated string | ||
// using the 'val' method. | ||
if( $.type(to) === "string" ) { | ||
// of decimals. This way, the developer can also | ||
// input a comma separated string. | ||
if( $.type(to) === 'string' ) { | ||
to = to.replace(',', '.'); | ||
@@ -1057,55 +1319,109 @@ } | ||
// Calculate the new handle position | ||
to = percentage.to( range, parseFloat( to ) ); | ||
to = toPercentage( settings['range'], parseFloat( to ) ); | ||
// Set handle to new location, and make sure developer | ||
// input is always accepted. The 'trusted' flag indicates | ||
// input that is not coming from user facing elements. | ||
result = setHandle( handle, to, modifiers.trusted ); | ||
// The 'val' method allows for an external modifier, | ||
// to specify a request for an 'set' event. | ||
if( modifiers.trigger ) { | ||
call( handle.data('nui').options.set | ||
,target ); | ||
// 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. | ||
if( !result ){ | ||
// 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 ); | ||
} | ||
// Get the 'store' object, which can be an input | ||
// element or a wrapper around a 'data' call. | ||
value = handle.data('store').val(); | ||
// Optionally trigger the 'set' event. | ||
if( set === true ) { | ||
call( settings['set'], $(this) ); | ||
} | ||
} | ||
}); | ||
} | ||
// Get the value for the current position. | ||
current = percentage.is( | ||
range | ||
,handle[0].gPct(handle.data('nui').style) | ||
); | ||
// Unbind all attached events, remove classed and HTML. | ||
function destroy ( target ) { | ||
// 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( value !== current ){ | ||
handle.data('store').val( format( current, 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'); | ||
} | ||
// Overwrite the native jQuery 'val' function | ||
// with a simple handler. noUiSlider will use the internal | ||
// value method, anything else will use the standard method. | ||
// 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)['noUiSlider']( 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(){ | ||
return this.hasClass(clsList[6]) | ||
? val.apply(this, arguments) | ||
: $VAL.apply(this, arguments); | ||
// 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 create.call( this, options ); | ||
return ( rebuild ? build : create ).call( this, options ); | ||
}; | ||
}( $ )); | ||
}( window['jQuery'] || window['Zepto'] )); |
{ | ||
"name": "nouislider-browser", | ||
"version": "4.2.2-1", | ||
"version": "5.0.0-1", | ||
"description": "npm package for nouislider", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
# noUiSlider | ||
noUiSlider is lightweight plugin that was developed to be a jQuery UI alternative. It features cross-browser support, a `just-another-input-type` style of getting and setting values, a wide range of options and support for a bunch off touch devices. It works wonders on Android phones, iPhone & iPad, Windows phone and touch-screen laptops and tablets. It works excellent on the desktop too; All modern browsers and IE7+ are supported. The end result? A lean, extendible and bloat-less plugin that'll just do its job. To add even more flexibility, noUiSlider is compatible with both jQuery and Zepto.js. Oh, and the licensing terms are simple: [just do what you want](http://refreshless.com/nouislider/terms-of-use). | ||
noUiSlider is lightweight plugin, developed to be a jQuery UI alternative. It features cross-browser support, a `just-another-input-type` style of getting and setting values, a wide range of options and support for a bunch of touch devices. It works wonders on Android phones, iPhone & iPad, Windows phone and touch-screen laptops and tablets. It works excellent on the desktop too; All modern browsers and IE7+ are supported. The end result? A lean, extendible and bloat-less plugin that'll just do its job. To add even more flexibility, noUiSlider is compatible with both jQuery and Zepto.js. Oh, and the licensing terms are simple: [just do what you want](http://refreshless.com/nouislider/terms-of-use). | ||
@@ -8,3 +8,3 @@ Documentation | ||
An extended documentation, including **examples**, **options** and **configuration details**, is available here: [noUiSlider documentation](http://refreshless.com/nouislider/). | ||
An extensive documentation, including **examples**, **options** and **configuration details**, is available here: [noUiSlider documentation](http://refreshless.com/nouislider/). | ||
@@ -14,5 +14,13 @@ Changes | ||
**Changelog for version 4.2.2:** | ||
+ Changed implementation of pointerEvents to be compatible with IE11. | ||
**Changelog for version 5.0.0:** | ||
**Please note:** noUiSlider 5 is a *major* revision, which means it isn't 100% compatible with version 4. The Javascript API is **compatible**, but your **stylesheet might break**. | ||
+ Added support for dragging the slider range. | ||
+ Added option to disable 'tap'. | ||
+ Added `extend` settings, which allows for designs where the handles fit within the slider bar. | ||
+ Added `rebuild` method. | ||
+ Brand new design, improved class structure. | ||
+ Compatibility with Google Closure compiler in Advanced mode. | ||
Version numbering | ||
@@ -26,26 +34,14 @@ ------------------------------ | ||
**CSS** ([CSSMinifier](http://cssminifier.com/)) | ||
The stylesheet is trimmed of whitespace and comments to provide a `min` version. | ||
The stylesheet is trimmed to remove whitespace and comments to provide a `min` version. | ||
**JS** ([Google Closure Compiler](http://closure-compiler.appspot.com/home)) | ||
The plugin is compressed using the Google Closure compiler, using the 'simple' optimization option. | ||
The plugin is compressed using the Google Closure compiler. The source was adapted to facilitate the `ADVANCED_OPTIMIZATIONS` level. | ||
**Code** ([JsLint](http://jslint.com/)) | ||
The plugin code is checked using JsLint, with the following options: | ||
``` | ||
browser: true | ||
devel: true | ||
plusplus: true | ||
unparam: true | ||
white: true | ||
``` | ||
The plugin code is checked using JsLint. Any remaining errors and warnings are intentional. | ||
Please note that while some errors remain without these options, they are merely differences in coding style. Using `++` for example, is in my opinion very clear in a `for` loop. Some jQuery methods offer callbacks noUiSlider doesn't require, thus requiring `unparam`, and the `devel` option is required for the `console` statements. | ||
Known issues | ||
------------ | ||
There are some minor issues remaining in noUiSlider. It is a priority to fix these issues, but they may be fixed by browser changes in the future. | ||
There are some minor issues remaining in noUiSlider. It is a priority to fix these issues, but they may be fixed by browser updates in the future. | ||
+ Firefox will prefer scrolling to dragging the slider on touch events. The `preventDefault()` call that prevents this in other browser seems to fail here. | ||
+ Safari for windows has the same issue, but only on vertical scrolling. | ||
+ Firefox and Safari on Windows will emulate mouse-events on touch screens, but prefer scrolling to dragging the slider. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45443
1313
45
1