diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 6bb829f6..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -/distribute/* -diff diff --git a/Gruntfile.js b/Gruntfile.js index 663f1456..1b161672 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -86,6 +86,9 @@ module.exports = function(grunt) { }, uglify: { all: { + options: { + banner: VERSION_TEMPLATE + }, files: { 'distribute/jquery.nouislider.min.js': 'distribute/jquery.nouislider.js', 'distribute/jquery.nouislider.all.min.js': 'distribute/jquery.nouislider.all.js' diff --git a/README.md b/README.md index ab4247a8..380797ff 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,17 @@ An extensive documentation, including **examples**, **options** and **configurat Changelog --------- -Latest changes: +###7.0.3: ++ Fixed an issue with Link on single-handle RTL sliders. ++ Added minified files for Bower users. ++ Version information in minified JS. + +###7.0.2: + Fixed an issue with the handle `z-index`. (#333) + Added pips formatting. (#330) + Added Grunt-based tasks. -**Note for Bower users:** -The repository no longers contains any minified files. As you are using npm anyway, simply run `npm install` & `grunt create` to generate them. - +###7.x noUiSlider is currently on version 7. This version contains significant changes from 6, improving various aspects and moving some features in their own module. + All serialization features are now supported by my new project, [libLink](http://refreshless.com/liblink/). + All number formatting features have been moved into the [wNumb formatting library](http://refreshless.com/wnumb/). diff --git a/bower.json b/bower.json index 86476bd9..f2806313 100644 --- a/bower.json +++ b/bower.json @@ -11,6 +11,11 @@ "input", "slide" ], + "main": [ + "distribute/jquery.nouislider.all.min.js", + "distribute/jquery.nouislider.min.css", + "distribute/jquery.nouislider.pips.min.css" + ], "dependencies": { "jquery": ">= 1.7.0" }, diff --git a/distribute/jquery.nouislider.all.js b/distribute/jquery.nouislider.all.js new file mode 100644 index 00000000..2ed3f354 --- /dev/null +++ b/distribute/jquery.nouislider.all.js @@ -0,0 +1,2275 @@ +/*! noUiSlider - 7.0.3 - 2014-09-11 16:30:11 */ + +(function(){ + + 'use strict'; + +var +/** @const */ FormatOptions = [ + 'decimals', + 'thousand', + 'mark', + 'prefix', + 'postfix', + 'encoder', + 'decoder', + 'negativeBefore', + 'negative', + 'edit', + 'undo' +]; + +// General + + // Reverse a string + function strReverse ( a ) { + return a.split('').reverse().join(''); + } + + // Check if a string starts with a specified prefix. + function strStartsWith ( input, match ) { + return input.substring(0, match.length) === match; + } + + // Check is a string ends in a specified postfix. + function strEndsWith ( input, match ) { + return input.slice(-1 * match.length) === match; + } + + // Throw an error if formatting options are incompatible. + function throwEqualError( F, a, b ) { + if ( (F[a] || F[b]) && (F[a] === F[b]) ) { + throw new Error(a); + } + } + + // Check if a number is finite and not NaN + function isValidNumber ( input ) { + return typeof input === 'number' && isFinite( input ); + } + + // Provide rounding-accurate toFixed method. + function toFixed ( value, decimals ) { + var scale = Math.pow(10, decimals); + return ( Math.round(value * scale) / scale).toFixed( decimals ); + } + + +// Formatting + + // Accept a number as input, output formatted string. + function formatTo ( decimals, thousand, mark, prefix, postfix, encoder, decoder, negativeBefore, negative, edit, undo, input ) { + + var originalInput = input, inputIsNegative, inputPieces, inputBase, inputDecimals = '', output = ''; + + // Apply user encoder to the input. + // Expected outcome: number. + if ( encoder ) { + input = encoder(input); + } + + // Stop if no valid number was provided, the number is infinite or NaN. + if ( !isValidNumber(input) ) { + return false; + } + + // Rounding away decimals might cause a value of -0 + // when using very small ranges. Remove those cases. + if ( decimals && parseFloat(input.toFixed(decimals)) === 0 ) { + input = 0; + } + + // Formatting is done on absolute numbers, + // decorated by an optional negative symbol. + if ( input < 0 ) { + inputIsNegative = true; + input = Math.abs(input); + } + + // Reduce the number of decimals to the specified option. + if ( decimals !== false ) { + input = toFixed( input, decimals ); + } + + // Transform the number into a string, so it can be split. + input = input.toString(); + + // Break the number on the decimal separator. + if ( input.indexOf('.') !== -1 ) { + inputPieces = input.split('.'); + + inputBase = inputPieces[0]; + + if ( mark ) { + inputDecimals = mark + inputPieces[1]; + } + + } else { + + // If it isn't split, the entire number will do. + inputBase = input; + } + + // Group numbers in sets of three. + if ( thousand ) { + inputBase = strReverse(inputBase).match(/.{1,3}/g); + inputBase = strReverse(inputBase.join( strReverse( thousand ) )); + } + + // If the number is negative, prefix with negation symbol. + if ( inputIsNegative && negativeBefore ) { + output += negativeBefore; + } + + // Prefix the number + if ( prefix ) { + output += prefix; + } + + // Normal negative option comes after the prefix. Defaults to '-'. + if ( inputIsNegative && negative ) { + output += negative; + } + + // Append the actual number. + output += inputBase; + output += inputDecimals; + + // Apply the postfix. + if ( postfix ) { + output += postfix; + } + + // Run the output through a user-specified post-formatter. + if ( edit ) { + output = edit ( output, originalInput ); + } + + // All done. + return output; + } + + // Accept a sting as input, output decoded number. + function formatFrom ( decimals, thousand, mark, prefix, postfix, encoder, decoder, negativeBefore, negative, edit, undo, input ) { + + var originalInput = input, inputIsNegative, output = ''; + + // User defined pre-decoder. Result must be a non empty string. + if ( undo ) { + input = undo(input); + } + + // Test the input. Can't be empty. + if ( !input || typeof input !== 'string' ) { + return false; + } + + // If the string starts with the negativeBefore value: remove it. + // Remember is was there, the number is negative. + if ( negativeBefore && strStartsWith(input, negativeBefore) ) { + input = input.replace(negativeBefore, ''); + inputIsNegative = true; + } + + // Repeat the same procedure for the prefix. + if ( prefix && strStartsWith(input, prefix) ) { + input = input.replace(prefix, ''); + } + + // And again for negative. + if ( negative && strStartsWith(input, negative) ) { + input = input.replace(negative, ''); + inputIsNegative = true; + } + + // Remove the postfix. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice + if ( postfix && strEndsWith(input, postfix) ) { + input = input.slice(0, -1 * postfix.length); + } + + // Remove the thousand grouping. + if ( thousand ) { + input = input.split(thousand).join(''); + } + + // Set the decimal separator back to period. + if ( mark ) { + input = input.replace(mark, '.'); + } + + // Prepend the negative symbol. + if ( inputIsNegative ) { + output += '-'; + } + + // Add the number + output += input; + + // Trim all non-numeric characters (allow '.' and '-'); + output = output.replace(/[^0-9\.\-.]/g, ''); + + // The value contains no parse-able number. + if ( output === '' ) { + return false; + } + + // Covert to number. + output = Number(output); + + // Run the user-specified post-decoder. + if ( decoder ) { + output = decoder(output); + } + + // Check is the output is valid, otherwise: return false. + if ( !isValidNumber(output) ) { + return false; + } + + return output; + } + + +// Framework + + // Validate formatting options + function validate ( inputOptions ) { + + var i, optionName, optionValue, + filteredOptions = {}; + + for ( i = 0; i < FormatOptions.length; i+=1 ) { + + optionName = FormatOptions[i]; + optionValue = inputOptions[optionName]; + + if ( optionValue === undefined ) { + + // Only default if negativeBefore isn't set. + if ( optionName === 'negative' && !filteredOptions.negativeBefore ) { + filteredOptions[optionName] = '-'; + // Don't set a default for mark when 'thousand' is set. + } else if ( optionName === 'mark' && filteredOptions.thousand !== '.' ) { + filteredOptions[optionName] = '.'; + } else { + filteredOptions[optionName] = false; + } + + // Floating points in JS are stable up to 7 decimals. + } else if ( optionName === 'decimals' ) { + if ( optionValue >= 0 && optionValue < 8 ) { + filteredOptions[optionName] = optionValue; + } else { + throw new Error(optionName); + } + + // These options, when provided, must be functions. + } else if ( optionName === 'encoder' || optionName === 'decoder' || optionName === 'edit' || optionName === 'undo' ) { + if ( typeof optionValue === 'function' ) { + filteredOptions[optionName] = optionValue; + } else { + throw new Error(optionName); + } + + // Other options are strings. + } else { + + if ( typeof optionValue === 'string' ) { + filteredOptions[optionName] = optionValue; + } else { + throw new Error(optionName); + } + } + } + + // Some values can't be extracted from a + // string if certain combinations are present. + throwEqualError(filteredOptions, 'mark', 'thousand'); + throwEqualError(filteredOptions, 'prefix', 'negative'); + throwEqualError(filteredOptions, 'prefix', 'negativeBefore'); + + return filteredOptions; + } + + // Pass all options as function arguments + function passAll ( options, method, input ) { + var i, args = []; + + // Add all options in order of FormatOptions + for ( i = 0; i < FormatOptions.length; i+=1 ) { + args.push(options[FormatOptions[i]]); + } + + // Append the input, then call the method, presenting all + // options as arguments. + args.push(input); + return method.apply('', args); + } + + /** @constructor */ + function wNumb ( options ) { + + if ( !(this instanceof wNumb) ) { + return new wNumb ( options ); + } + + if ( typeof options !== "object" ) { + return; + } + + options = validate(options); + + // Call 'formatTo' with proper arguments. + this.to = function ( input ) { + return passAll(options, formatTo, input); + }; + + // Call 'formatFrom' with proper arguments. + this.from = function ( input ) { + return passAll(options, formatFrom, input); + }; + } + + /** @export */ + window.wNumb = wNumb; + +}()); + +/*jslint browser: true */ +/*jslint white: true */ + +(function( $ ){ + + 'use strict'; + +// Helpers + + // Test in an object is an instance of jQuery or Zepto. + function isInstance ( a ) { + return a instanceof $ || ( $.zepto && $.zepto.isZ(a) ); + } + + +// Link types + + function fromPrefix ( target, method ) { + + // If target is a string, a new hidden input will be created. + if ( typeof target === 'string' && target.indexOf('-inline-') === 0 ) { + + // By default, use the 'html' method. + this.method = method || 'html'; + + // Use jQuery to create the element + this.target = this.el = $( target.replace('-inline-', '') || '
' ); + + return true; + } + } + + function fromString ( target ) { + + // If the string doesn't begin with '-', which is reserved, add a new hidden input. + if ( typeof target === 'string' && target.indexOf('-') !== 0 ) { + + this.method = 'val'; + + var element = document.createElement('input'); + element.name = target; + element.type = 'hidden'; + this.target = this.el = $(element); + + return true; + } + } + + function fromFunction ( target ) { + + // The target can also be a function, which will be called. + if ( typeof target === 'function' ) { + this.target = false; + this.method = target; + + return true; + } + } + + function fromInstance ( target, method ) { + + if ( isInstance( target ) && !method ) { + + // If a jQuery/Zepto input element is provided, but no method is set, + // the element can assume it needs to respond to 'change'... + if ( target.is('input, select, textarea') ) { + + // Default to .val if this is an input element. + this.method = 'val'; + + // Fire the API changehandler when the target changes. + this.target = target.on('change.liblink', this.changeHandler); + + } else { + + this.target = target; + + // If no method is set, and we are not auto-binding an input, default to 'html'. + this.method = 'html'; + } + + return true; + } + } + + function fromInstanceMethod ( target, method ) { + + // The method must exist on the element. + if ( isInstance( target ) && + (typeof method === 'function' || + (typeof method === 'string' && target[method])) + ) { + this.method = method; + this.target = target; + + return true; + } + } + +var +/** @const */ + creationFunctions = [fromPrefix, fromString, fromFunction, fromInstance, fromInstanceMethod]; + + +// Link Instance + +/** @constructor */ + function Link ( target, method, format ) { + + var that = this, valid = false; + + // Forward calls within scope. + this.changeHandler = function ( changeEvent ) { + var decodedValue = that.formatInstance.from( $(this).val() ); + + // If the value is invalid, stop this event, as well as it's propagation. + if ( decodedValue === false || isNaN(decodedValue) ) { + + // Reset the value. + $(this).val(that.lastSetValue); + return false; + } + + that.changeHandlerMethod.call( '', changeEvent, decodedValue ); + }; + + // See if this Link needs individual targets based on its usage. + // If so, return the element that needs to be copied by the + // implementing interface. + // Default the element to false. + this.el = false; + + // Store the formatter, or use the default. + this.formatInstance = format; + + // Try all Link types. + /*jslint unparam: true*/ + $.each(creationFunctions, function(i, fn){ + valid = fn.call(that, target, method); + return !valid; + }); + /*jslint unparam: false*/ + + // Nothing matched, throw error. + if ( !valid ) { + throw new RangeError("(Link) Invalid Link."); + } + } + + // Provides external items with the object value. + Link.prototype.set = function ( value ) { + + // Ignore the value, so only the passed-on arguments remain. + var args = Array.prototype.slice.call( arguments ), + additionalArgs = args.slice(1); + + // Store some values. The actual, numerical value, + // the formatted value and the parameters for use in 'resetValue'. + // Slice additionalArgs to break the relation. + this.lastSetValue = this.formatInstance.to( value ); + + // Prepend the value to the function arguments. + additionalArgs.unshift( + this.lastSetValue + ); + + // When target is undefined, the target was a function. + // In that case, provided the object as the calling scope. + // Branch between writing to a function or an object. + ( typeof this.method === 'function' ? + this.method : + this.target[ this.method ] ).apply( this.target, additionalArgs ); + }; + + +// Developer API + +/** @constructor */ + function LinkAPI ( origin ) { + this.items = []; + this.elements = []; + this.origin = origin; + } + + LinkAPI.prototype.push = function( item, element ) { + this.items.push(item); + + // Prevent 'false' elements + if ( element ) { + this.elements.push(element); + } + }; + + LinkAPI.prototype.reconfirm = function ( flag ) { + var i; + for ( i = 0; i < this.elements.length; i += 1 ) { + this.origin.LinkConfirm(flag, this.elements[i]); + } + }; + + LinkAPI.prototype.remove = function ( flag ) { + var i; + for ( i = 0; i < this.items.length; i += 1 ) { + this.items[i].target.off('.liblink'); + } + for ( i = 0; i < this.elements.length; i += 1 ) { + this.elements[i].remove(); + } + }; + + LinkAPI.prototype.change = function ( value ) { + + if ( this.origin.LinkIsEmitting ) { + return false; + } + + this.origin.LinkIsEmitting = true; + + var args = Array.prototype.slice.call( arguments, 1 ), i; + args.unshift( value ); + + // Write values to serialization Links. + // Convert the value to the correct relative representation. + for ( i = 0; i < this.items.length; i += 1 ) { + this.items[i].set.apply(this.items[i], args); + } + + this.origin.LinkIsEmitting = false; + }; + + +// jQuery plugin + + function binder ( flag, target, method, format ){ + + if ( flag === 0 ) { + flag = this.LinkDefaultFlag; + } + + // Create a list of API's (if it didn't exist yet); + if ( !this.linkAPI ) { + this.linkAPI = {}; + } + + // Add an API point. + if ( !this.linkAPI[flag] ) { + this.linkAPI[flag] = new LinkAPI(this); + } + + var linkInstance = new Link ( target, method, format || this.LinkDefaultFormatter ); + + // Default the calling scope to the linked object. + if ( !linkInstance.target ) { + linkInstance.target = $(this); + } + + // If the Link requires creation of a new element, + // Pass the element and request confirmation to get the changehandler. + // Set the method to be called when a Link changes. + linkInstance.changeHandlerMethod = this.LinkConfirm( flag, linkInstance.el ); + + // Store the linkInstance in the flagged list. + this.linkAPI[flag].push( linkInstance, linkInstance.el ); + + // Now that Link have been connected, request an update. + this.LinkUpdate( flag ); + } + + /** @export */ + $.fn.Link = function( flag ){ + + var that = this; + + // Delete all linkAPI + if ( flag === false ) { + + return that.each(function(){ + + // .Link(false) can be called on elements without Links. + // When that happens, the objects can't be looped. + if ( !this.linkAPI ) { + return; + } + + $.map(this.linkAPI, function(api){ + api.remove(); + }); + + delete this.linkAPI; + }); + } + + if ( flag === undefined ) { + + flag = 0; + + } else if ( typeof flag !== 'string') { + + throw new Error("Flag must be string."); + } + + return { + to: function( a, b, c ){ + return that.each(function(){ + binder.call(this, flag, a, b, c); + }); + } + }; + }; + +}( window.jQuery || window.Zepto )); + +/*jslint browser: true */ +/*jslint white: true */ + +(function( $ ){ + + 'use strict'; + + + // Removes duplicates from an array. + function unique(array) { + return $.grep(array, function(el, index) { + return index === $.inArray(el, array); + }); + } + + // Round a value to the closest 'to'. + function closest ( value, to ) { + return Math.round(value / to) * to; + } + + // Checks whether a value is numerical. + function isNumeric ( a ) { + return typeof a === 'number' && !isNaN( a ) && isFinite( a ); + } + + // Rounds a number to 7 supported decimals. + function accurateNumber( number ) { + var p = Math.pow(10, 7); + return Number((Math.round(number*p)/p).toFixed(7)); + } + + // Sets a class and removes it after [duration] ms. + function addClassFor ( element, className, duration ) { + element.addClass(className); + setTimeout(function(){ + element.removeClass(className); + }, duration); + } + + // Limits a value to 0 - 100 + function limit ( a ) { + return Math.max(Math.min(a, 100), 0); + } + + // Wraps a variable as an array, if it isn't one yet. + function asArray ( a ) { + return $.isArray(a) ? a : [a]; + } + + + var + // Cache the document selector; + /** @const */ + doc = $(document), + // Make a backup of the original jQuery/Zepto .val() method. + /** @const */ + $val = $.fn.val, + // Namespace for binding and unbinding slider events; + /** @const */ + namespace = '.nui', + // Determine the events to bind. IE11 implements pointerEvents without + // a prefix, which breaks compatibility with the IE10 implementation. + /** @const */ + 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' + }, + // Re-usable list of classes; + /** @const */ + Classes = [ +/* 0 */ 'noUi-target' +/* 1 */ ,'noUi-base' +/* 2 */ ,'noUi-origin' +/* 3 */ ,'noUi-handle' +/* 4 */ ,'noUi-horizontal' +/* 5 */ ,'noUi-vertical' +/* 6 */ ,'noUi-background' +/* 7 */ ,'noUi-connect' +/* 8 */ ,'noUi-ltr' +/* 9 */ ,'noUi-rtl' +/* 10 */ ,'noUi-dragable' +/* 11 */ ,'' +/* 12 */ ,'noUi-state-drag' +/* 13 */ ,'' +/* 14 */ ,'noUi-state-tap' +/* 15 */ ,'noUi-active' +/* 16 */ ,'' +/* 17 */ ,'noUi-stacking' + ]; + + +// Value calculation + + // Determine the size of a sub-range in relation to a full range. + function subRangeRatio ( pa, pb ) { + return (100 / (pb - pa)); + } + + // (percentage) How many percent is this value of this range? + function fromPercentage ( range, value ) { + return (value * 100) / ( range[1] - range[0] ); + } + + // (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]; + } + + +// Range conversion + + function getJ ( value, arr ) { + + var j = 1; + + while ( value >= arr[j] ){ + j += 1; + } + + return j; + } + + // (percentage) Input a value, find where, on a scale of 0-100, it applies. + function toStepping ( xVal, xPct, value ) { + + if ( value >= xVal.slice(-1)[0] ){ + return 100; + } + + var j = getJ( value, xVal ), va, vb, pa, pb; + + va = xVal[j-1]; + vb = xVal[j]; + pa = xPct[j-1]; + pb = xPct[j]; + + return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb)); + } + + // (value) Input a percentage, find where it is on the specified range. + function fromStepping ( xVal, xPct, value ) { + + // There is no range group that fits 100 + if ( value >= 100 ){ + return xVal.slice(-1)[0]; + } + + var j = getJ( value, xPct ), va, vb, pa, pb; + + va = xVal[j-1]; + vb = xVal[j]; + pa = xPct[j-1]; + pb = xPct[j]; + + return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb)); + } + + // (percentage) Get the step that applies at a certain value. + function getStep ( xPct, xSteps, snap, value ) { + + if ( value === 100 ) { + return value; + } + + var j = getJ( value, xPct ), a, b; + + // If 'snap' is set, steps are used as fixed points on the slider. + if ( snap ) { + + a = xPct[j-1]; + b = xPct[j]; + + // Find the closest position, a or b. + if ((value - a) > ((b-a)/2)){ + return b; + } + + return a; + } + + if ( !xSteps[j-1] ){ + return value; + } + + return xPct[j-1] + closest( + value - xPct[j-1], + xSteps[j-1] + ); + } + + +// Entry parsing + + function handleEntryPoint ( index, value, that ) { + + var percentage; + + // Wrap numerical input in an array. + if ( typeof value === "number" ) { + value = [value]; + } + + // Reject any invalid input, by testing whether value is an array. + if ( Object.prototype.toString.call( value ) !== '[object Array]' ){ + throw new Error("noUiSlider: 'range' contains invalid value."); + } + + // Covert min/max syntax to 0 and 100. + if ( index === 'min' ) { + percentage = 0; + } else if ( index === 'max' ) { + percentage = 100; + } else { + percentage = parseFloat( index ); + } + + // Check for correct input. + if ( !isNumeric( percentage ) || !isNumeric( value[0] ) ) { + throw new Error("noUiSlider: 'range' value isn't numeric."); + } + + // Store values. + that.xPct.push( percentage ); + that.xVal.push( value[0] ); + + // NaN will evaluate to false too, but to keep + // logging clear, set step explicitly. Make sure + // not to override the 'step' setting with false. + if ( !percentage ) { + if ( !isNaN( value[1] ) ) { + that.xSteps[0] = value[1]; + } + } else { + that.xSteps.push( isNaN(value[1]) ? false : value[1] ); + } + } + + function handleStepPoint ( i, n, that ) { + + // Ignore 'false' stepping. + if ( !n ) { + return true; + } + + // Factor to range ratio + that.xSteps[i] = fromPercentage([ + that.xVal[i] + ,that.xVal[i+1] + ], n) / subRangeRatio ( + that.xPct[i], + that.xPct[i+1] ); + } + + +// Interface + + // The interface to Spectrum handles all direction-based + // conversions, so the above values are unaware. + + function Spectrum ( entry, snap, direction, singleStep ) { + + this.xPct = []; + this.xVal = []; + this.xSteps = [ singleStep || false ]; + this.xNumSteps = [ false ]; + + this.snap = snap; + this.direction = direction; + + var that = this, index; + + // Loop all entries. + for ( index in entry ) { + if ( entry.hasOwnProperty(index) ) { + handleEntryPoint(index, entry[index], that); + } + } + + // Store the actual step values. + that.xNumSteps = that.xSteps.slice(0); + + for ( index in that.xNumSteps ) { + if ( that.xNumSteps.hasOwnProperty(index) ) { + handleStepPoint(Number(index), that.xNumSteps[index], that); + } + } + } + + Spectrum.prototype.getMargin = function ( value ) { + return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false; + }; + + Spectrum.prototype.toStepping = function ( value ) { + + value = toStepping( this.xVal, this.xPct, value ); + + // Invert the value if this is a right-to-left slider. + if ( this.direction ) { + value = 100 - value; + } + + return value; + }; + + Spectrum.prototype.fromStepping = function ( value ) { + + // Invert the value if this is a right-to-left slider. + if ( this.direction ) { + value = 100 - value; + } + + return accurateNumber(fromStepping( this.xVal, this.xPct, value )); + }; + + Spectrum.prototype.getStep = function ( value ) { + + // Find the proper step for rtl sliders by search in inverse direction. + // Fixes issue #262. + if ( this.direction ) { + value = 100 - value; + } + + value = getStep(this.xPct, this.xSteps, this.snap, value ); + + if ( this.direction ) { + value = 100 - value; + } + + return value; + }; + + Spectrum.prototype.getApplicableStep = function ( value ) { + + // If the value is 100%, return the negative step twice. + var j = getJ(value, this.xPct), offset = value === 100 ? 2 : 1; + return [this.xNumSteps[j-2], this.xVal[j-offset], this.xNumSteps[j-offset]]; + }; + + // Outside testing + Spectrum.prototype.convert = function ( value ) { + return this.getStep(this.toStepping(value)); + }; + +/* 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. */ + + /** @const */ + var defaultFormatter = { 'to': function( value ){ + return value.toFixed(2); + }, 'from': Number }; + + function testStep ( parsed, entry ) { + + if ( !isNumeric( entry ) ) { + throw new Error("noUiSlider: 'step' is not numeric."); + } + + // The step option can still be used to set stepping + // for linear sliders. Overwritten if set in 'range'. + parsed.singleStep = entry; + } + + function testRange ( parsed, entry ) { + + // Filter incorrect input. + if ( typeof entry !== 'object' || $.isArray(entry) ) { + throw new Error("noUiSlider: 'range' is not an object."); + } + + // Catch missing start or end. + if ( entry.min === undefined || entry.max === undefined ) { + throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'."); + } + + parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.dir, parsed.singleStep); + } + + function testStart ( parsed, entry ) { + + entry = asArray(entry); + + // Validate input. Values aren't tested, as the public .val method + // will always provide a valid location. + if ( !$.isArray( entry ) || !entry.length || entry.length > 2 ) { + throw new Error("noUiSlider: 'start' option is incorrect."); + } + + // Store the number of handles. + parsed.handles = entry.length; + + // When the slider is initialized, the .val method will + // be called with the start options. + parsed.start = entry; + } + + function testSnap ( parsed, entry ) { + + // Enforce 100% stepping within subranges. + parsed.snap = entry; + + if ( typeof entry !== 'boolean' ){ + throw new Error("noUiSlider: 'snap' option must be a boolean."); + } + } + + function testAnimate ( parsed, entry ) { + + // Enforce 100% stepping within subranges. + parsed.animate = entry; + + if ( typeof entry !== 'boolean' ){ + throw new Error("noUiSlider: 'animate' option must be a boolean."); + } + } + + function testConnect ( parsed, entry ) { + + if ( entry === 'lower' && parsed.handles === 1 ) { + parsed.connect = 1; + } else if ( entry === 'upper' && parsed.handles === 1 ) { + parsed.connect = 2; + } else if ( entry === true && parsed.handles === 2 ) { + parsed.connect = 3; + } else if ( entry === false ) { + parsed.connect = 0; + } else { + throw new Error("noUiSlider: 'connect' option doesn't match handle count."); + } + } + + function testOrientation ( parsed, entry ) { + + // Set orientation to an a numerical value for easy + // array selection. + switch ( entry ){ + case 'horizontal': + parsed.ort = 0; + break; + case 'vertical': + parsed.ort = 1; + break; + default: + throw new Error("noUiSlider: 'orientation' option is invalid."); + } + } + + function testMargin ( parsed, entry ) { + + if ( !isNumeric(entry) ){ + throw new Error("noUiSlider: 'margin' option must be numeric."); + } + + parsed.margin = parsed.spectrum.getMargin(entry); + + if ( !parsed.margin ) { + throw new Error("noUiSlider: 'margin' option is only supported on linear sliders."); + } + } + + function testLimit ( parsed, entry ) { + + if ( !isNumeric(entry) ){ + throw new Error("noUiSlider: 'limit' option must be numeric."); + } + + parsed.limit = parsed.spectrum.getMargin(entry); + + if ( !parsed.limit ) { + throw new Error("noUiSlider: 'limit' option is only supported on linear sliders."); + } + } + + function testDirection ( parsed, entry ) { + + // Set direction as a numerical value for easy parsing. + // Invert connection for RTL sliders, so that the proper + // handles get the connect/background classes. + switch ( entry ) { + case 'ltr': + parsed.dir = 0; + break; + case 'rtl': + parsed.dir = 1; + parsed.connect = [0,2,1,3][parsed.connect]; + break; + default: + throw new Error("noUiSlider: 'direction' option was not recognized."); + } + } + + function testBehaviour ( parsed, entry ) { + + // Make sure the input is a string. + if ( typeof entry !== 'string' ) { + throw new Error("noUiSlider: 'behaviour' must be a string containing options."); + } + + // Check if the string contains any keywords. + // None are required. + var tap = entry.indexOf('tap') >= 0, + drag = entry.indexOf('drag') >= 0, + fixed = entry.indexOf('fixed') >= 0, + snap = entry.indexOf('snap') >= 0; + + parsed.events = { + tap: tap || snap, + drag: drag, + fixed: fixed, + snap: snap + }; + } + + function testFormat ( parsed, entry ) { + + parsed.format = entry; + + // Any object with a to and from method is supported. + if ( typeof entry.to === 'function' && typeof entry.from === 'function' ) { + return true; + } + + throw new Error( "noUiSlider: 'format' requires 'to' and 'from' methods."); + } + + // Test all developer settings and parse to assumption-safe values. + function testOptions ( options ) { + + var parsed = { + margin: 0, + limit: 0, + animate: true, + format: defaultFormatter + }, tests; + + // Tests are executed in the order they are presented here. + tests = { + 'step': { r: false, t: testStep }, + 'start': { r: true, t: testStart }, + 'connect': { r: true, t: testConnect }, + 'direction': { r: true, t: testDirection }, + 'snap': { r: false, t: testSnap }, + 'animate': { r: false, t: testAnimate }, + 'range': { r: true, t: testRange }, + 'orientation': { r: false, t: testOrientation }, + 'margin': { r: false, t: testMargin }, + 'limit': { r: false, t: testLimit }, + 'behaviour': { r: true, t: testBehaviour }, + 'format': { r: false, t: testFormat } + }; + + // Set defaults where applicable. + options = $.extend({ + 'connect': false, + 'direction': 'ltr', + 'behaviour': 'tap', + 'orientation': 'horizontal' + }, options); + + // Run all options through a testing mechanism to ensure correct + // input. It should be noted that options might get modified to + // be handled properly. E.g. wrapping integers in arrays. + $.each( tests, function( name, test ){ + + // If the option isn't set, but it is required, throw an error. + if ( options[name] === undefined ) { + + if ( test.r ) { + throw new Error("noUiSlider: '" + name + "' is required."); + } + + return true; + } + + test.t( parsed, options[name] ); + }); + + // Pre-define the styles. + parsed.style = parsed.ort ? 'top' : 'left'; + + return parsed; + } + +// Class handling + + // Delimit proposed values for handle positions. + function getPositions ( a, b, delimit ) { + + // Add movement to current position. + var c = a + b[0], d = a + b[1]; + + // Only alter the other position on drag, + // not on standard sliding. + if ( delimit ) { + if ( c < 0 ) { + d += Math.abs(c); + } + if ( d > 100 ) { + c -= ( d - 100 ); + } + + // Limit values to 0 and 100. + return [limit(c), limit(d)]; + } + + return [c,d]; + } + + +// Event handling + + // Provide a clean event with standardized offset values. + function fixEvent ( e ) { + + // 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. Offset changes need to be + // made on an event specific basis. + var touch = e.type.indexOf('touch') === 0 + ,mouse = e.type.indexOf('mouse') === 0 + ,pointer = e.type.indexOf('pointer') === 0 + ,x,y, event = e; + + // IE10 implemented pointer events with a prefix; + if ( e.type.indexOf('MSPointer') === 0 ) { + pointer = true; + } + + // Get the originalEvent, if the event has been wrapped + // by jQuery. Zepto doesn't wrap the event. + if ( e.originalEvent ) { + e = e.originalEvent; + } + + if ( touch ) { + // noUiSlider supports one movement at a time, + // so we can select the first 'changedTouch'. + x = e.changedTouches[0].pageX; + y = e.changedTouches[0].pageY; + } + + if ( mouse || pointer ) { + + // Polyfill the pageXOffset and pageYOffset + // variables for IE7 and IE8; + if( !pointer && window.pageXOffset === undefined ){ + window.pageXOffset = document.documentElement.scrollLeft; + window.pageYOffset = document.documentElement.scrollTop; + } + + x = e.clientX + window.pageXOffset; + y = e.clientY + window.pageYOffset; + } + + event.points = [x, y]; + event.cursor = mouse; + + return event; + } + + +// DOM additions + + // Append a handle to the base. + function addHandle ( direction, index ) { + + var handle = $('
').addClass( Classes[2] ), + additions = [ '-lower', '-upper' ]; + + if ( direction ) { + additions.reverse(); + } + + handle.children().addClass( + Classes[3] + " " + Classes[3]+additions[index] + ); + + return handle; + } + + // Add the proper connection classes. + function addConnection ( connect, target, handles ) { + + // 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 ( connect ) { + case 1: target.addClass( Classes[7] ); + handles[0].addClass( Classes[6] ); + break; + case 3: handles[1].addClass( Classes[6] ); + /* falls through */ + case 2: handles[0].addClass( Classes[7] ); + /* falls through */ + case 0: target.addClass(Classes[6]); + break; + } + } + + // Add handles to the slider base. + function addHandles ( nrHandles, direction, base ) { + + var index, handles = []; + + // Append handles. + for ( index = 0; index < nrHandles; index += 1 ) { + + // Keep a list of all added handles. + handles.push( addHandle( direction, index ).appendTo(base) ); + } + + return handles; + } + + // Initialize a single slider. + function addSlider ( direction, orientation, target ) { + + // Apply classes and data to the target. + target.addClass([ + Classes[0], + Classes[8 + direction], + Classes[4 + orientation] + ].join(' ')); + + return $('
').appendTo(target).addClass( Classes[1] ); + } + +function closure ( target, options, originalOptions ){ + +// Internal variables + + // All variables local to 'closure' are marked $. + var $Target = $(target), + $Locations = [-1, -1], + $Base, + $Handles, + $Spectrum = options.spectrum, + $Values = [], + // libLink. For rtl sliders, 'lower' and 'upper' should not be inverted + // for one-handle sliders, so trim 'upper' it that case. + triggerPos = ['lower', 'upper'].slice(0, options.handles); + + // Invert the libLink connection for rtl sliders. + if ( options.dir ) { + triggerPos.reverse(); + } + +// Helpers + + // Shorthand for base dimensions. + function baseSize ( ) { + return $Base[['width', 'height'][options.ort]](); + } + + // External event handling + function fireEvents ( events ) { + + // Use the external api to get the values. + // Wrap the values in an array, as .trigger takes + // only one additional argument. + var index, values = [ $Target.val() ]; + + for ( index = 0; index < events.length; index += 1 ){ + $Target.trigger(events[index], values); + } + } + + // Returns the input array, respecting the slider direction configuration. + function inSliderOrder ( values ) { + + // If only one handle is used, return a single value. + if ( values.length === 1 ){ + return values[0]; + } + + if ( options.dir ) { + return values.reverse(); + } + + return values; + } + +// libLink integration + + // Create a new function which calls .val on input change. + function createChangeHandler ( trigger ) { + return function ( ignore, value ){ + // Determine which array position to 'null' based on 'trigger'. + $Target.val( [ trigger ? null : value, trigger ? value : null ], true ); + }; + } + + // Called by libLink when it wants a set of links updated. + function linkUpdate ( flag ) { + + var trigger = $.inArray(flag, triggerPos); + + // The API might not have been set yet. + if ( $Target[0].linkAPI && $Target[0].linkAPI[flag] ) { + $Target[0].linkAPI[flag].change( + $Values[trigger], + $Handles[trigger].children(), + $Target + ); + } + } + + // Called by libLink to append an element to the slider. + function linkConfirm ( flag, element ) { + + // Find the trigger for the passed flag. + var trigger = $.inArray(flag, triggerPos); + + // If set, append the element to the handle it belongs to. + if ( element ) { + element.appendTo( $Handles[trigger].children() ); + } + + // The public API is reversed for rtl sliders, so the changeHandler + // should not be aware of the inverted trigger positions. + // On rtl slider with one handle, 'lower' should be used. + if ( options.dir && options.handles > 1 ) { + trigger = trigger === 1 ? 0 : 1; + } + + return createChangeHandler( trigger ); + } + + // Place elements back on the slider. + function reAppendLink ( ) { + + var i, flag; + + // The API keeps a list of elements: we can re-append them on rebuild. + for ( i = 0; i < triggerPos.length; i += 1 ) { + if ( this.linkAPI && this.linkAPI[(flag = triggerPos[i])] ) { + this.linkAPI[flag].reconfirm(flag); + } + } + } + + target.LinkUpdate = linkUpdate; + target.LinkConfirm = linkConfirm; + target.LinkDefaultFormatter = options.format; + target.LinkDefaultFlag = 'lower'; + + target.reappend = reAppendLink; + + + // Handler for attaching events trough a proxy. + function attach ( events, element, callback, data ) { + + // This function can be used to 'filter' events to the slider. + + // Add the noUiSlider namespace to all events. + events = events.replace( /\s/g, namespace + ' ' ) + namespace; + + // Bind a closure on the target. + return element.on( events, function( e ){ + + // jQuery and Zepto (1) handle unset attributes differently, + // but always falsy; #208 + if ( !!$Target.attr('disabled') ) { + return false; + } + + // Stop if an active 'tap' transition is taking place. + if ( $Target.hasClass( Classes[14] ) ) { + return false; + } + + e = fixEvent(e); + e.calcPoint = e.points[ options.ort ]; + + // Call the event handler with the event [ and additional data ]. + callback ( e, data ); + }); + } + + // Handle movement on document for handle and range drag. + function move ( event, data ) { + + var handles = data.handles || $Handles, positions, state = false, + proposal = ((event.calcPoint - data.start) * 100) / baseSize(), + h = handles[0][0] !== $Handles[0][0] ? 1 : 0; + + // Calculate relative positions for the handles. + positions = getPositions( proposal, data.positions, handles.length > 1); + + state = setHandle ( handles[0], positions[h], handles.length === 1 ); + + if ( handles.length > 1 ) { + state = setHandle ( handles[1], positions[h?0:1], false ) || state; + } + + // Fire the 'slide' event if any handle moved. + if ( state ) { + fireEvents(['slide']); + } + } + + // Unbind move events on document, call callbacks. + function end ( event ) { + + // The handle is no longer active, so remove the class. + $('.' + Classes[15]).removeClass(Classes[15]); + + // Remove cursor styles and text-selection events bound to the body. + if ( event.cursor ) { + $('body').css('cursor', '').off( namespace ); + } + + // Unbind the move and end events, which are added on 'start'. + doc.off( namespace ); + + // Remove dragging class. + $Target.removeClass(Classes[12]); + + // Fire the change and set events. + fireEvents(['set', 'change']); + } + + // Bind move events on document. + function start ( event, data ) { + + // Mark the handle as 'active' so it can be styled. + if( data.handles.length === 1 ) { + data.handles[0].children().addClass(Classes[15]); + } + + // A drag should never propagate up to the 'tap' event. + event.stopPropagation(); + + // Attach the move event. + attach ( actions.move, doc, move, { + start: event.calcPoint, + handles: data.handles, + positions: [ + $Locations[0], + $Locations[$Handles.length - 1] + ] + }); + + // Unbind all movement when the drag ends. + attach ( actions.end, doc, end, null ); + + // Text selection isn't an issue on touch devices, + // so adding cursor styles can be skipped. + 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 ( $Handles.length > 1 ) { + $Target.addClass(Classes[12]); + } + + // Prevent text selection when dragging the handles. + $('body').on('selectstart' + namespace, false); + } + } + + // Move closest handle to tapped location. + function tap ( event ) { + + var location = event.calcPoint, total = 0, to; + + // The tap event shouldn't propagate up and cause 'edge' to run. + event.stopPropagation(); + + // Add up the handle offsets. + $.each( $Handles, function(){ + total += this.offset()[ options.style ]; + }); + + // Find the handle closest to the tapped position. + total = ( location < total/2 || $Handles.length === 1 ) ? 0 : 1; + + location -= $Base.offset()[ options.style ]; + + // Calculate the new position. + to = ( location * 100 ) / baseSize(); + + if ( !options.events.snap ) { + // Flag the slider as it is now in a transitional state. + // Transition takes 300 ms, so re-enable the slider afterwards. + addClassFor( $Target, Classes[14], 300 ); + } + + // Find the closest handle and calculate the tapped point. + // The set handle to the new position. + setHandle( $Handles[total], to ); + + fireEvents(['slide', 'set', 'change']); + + if ( options.events.snap ) { + start(event, { handles: [$Handles[total]] }); + } + } + + // Attach events to several slider parts. + function events ( behaviour ) { + + var i, drag; + + // Attach the standard drag event to the handles. + if ( !behaviour.fixed ) { + + for ( i = 0; i < $Handles.length; i += 1 ) { + + // These events are only bound to the visual handle + // element, not the 'real' origin element. + attach ( actions.start, $Handles[i].children(), start, { + handles: [ $Handles[i] ] + }); + } + } + + // Attach the tap event to the slider base. + if ( behaviour.tap ) { + + attach ( actions.start, $Base, tap, { + handles: $Handles + }); + } + + // Make the range dragable. + if ( behaviour.drag ){ + + drag = $Base.find( '.' + Classes[7] ).addClass( Classes[10] ); + + // 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 ( behaviour.fixed ) { + drag = drag.add($Base.children().not( drag ).children()); + } + + attach ( actions.start, drag, start, { + handles: $Handles + }); + } + } + + + // Test suggested values and apply margin, step. + function setHandle ( handle, to, noLimitOption ) { + + var trigger = handle[0] !== $Handles[0][0] ? 1 : 0, + lowerMargin = $Locations[0] + options.margin, + upperMargin = $Locations[1] - options.margin, + lowerLimit = $Locations[0] + options.limit, + upperLimit = $Locations[1] - options.limit; + + // For sliders with multiple handles, + // limit movement to the other handle. + // Apply the margin option by adding it to the handle positions. + if ( $Handles.length > 1 ) { + to = trigger ? Math.max( to, lowerMargin ) : Math.min( to, upperMargin ); + } + + // The limit option has the opposite effect, limiting handles to a + // maximum distance from another. Limit must be > 0, as otherwise + // handles would be unmoveable. 'noLimitOption' is set to 'false' + // for the .val() method, except for pass 4/4. + if ( noLimitOption !== false && options.limit && $Handles.length > 1 ) { + to = trigger ? Math.min ( to, lowerLimit ) : Math.max( to, upperLimit ); + } + + // Handle the step option. + to = $Spectrum.getStep( to ); + + // Limit to 0/100 for .val input, trim anything beyond 7 digits, as + // JavaScript has some issues in its floating point implementation. + to = limit(parseFloat(to.toFixed(7))); + + // Return false if handle can't move. + if ( to === $Locations[trigger] ) { + return false; + } + + // Set the handle to the new position. + handle.css( options.style, to + '%' ); + + // Force proper handle stacking + if ( handle.is(':first-child') ) { + handle.toggleClass(Classes[17], to > 50 ); + } + + // Update locations. + $Locations[trigger] = to; + + // Convert the value to the slider stepping/range. + $Values[trigger] = $Spectrum.fromStepping( to ); + + linkUpdate(triggerPos[trigger]); + + return true; + } + + // Loop values from value method and apply them. + function setValues ( count, values ) { + + var i, trigger, to; + + // With the limit option, we'll need another limiting pass. + if ( options.limit ) { + count += 1; + } + + // 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. + for ( i = 0; i < count; i += 1 ) { + + trigger = i%2; + + // Get the current argument from the array. + to = values[trigger]; + + // Setting with null indicates an 'ignore'. + // Inputting 'false' is invalid. + if ( to !== null && to !== false ) { + + // If a formatted number was passed, attemt to decode it. + if ( typeof to === 'number' ) { + to = String(to); + } + + to = options.format.from( to ); + + // Request an update for all links if the value was invalid. + // Do so too if setting the handle fails. + if ( to === false || isNaN(to) || setHandle( $Handles[trigger], $Spectrum.toStepping( to ), i === (3 - options.dir) ) === false ) { + + linkUpdate(triggerPos[trigger]); + } + } + } + } + + // Set the slider value. + function valueSet ( input ) { + + // LibLink: don't accept new values when currently emitting changes. + if ( $Target[0].LinkIsEmitting ) { + return this; + } + + var count, values = asArray( input ); + + // The RTL settings is implemented by reversing the front-end, + // internal mechanisms are the same. + if ( options.dir && options.handles > 1 ) { + values.reverse(); + } + + // Animation is optional. + // Make sure the initial values where set before using animated + // placement. (no report, unit testing); + if ( options.animate && $Locations[0] !== -1 ) { + addClassFor( $Target, Classes[14], 300 ); + } + + // Determine how often to set the handles. + count = $Handles.length > 1 ? 3 : 1; + + if ( values.length === 1 ) { + count = 1; + } + + setValues ( count, values ); + + // Fire the 'set' event. As of noUiSlider 7, + // this is no longer optional. + fireEvents(['set']); + + return this; + } + + // Get the slider value. + function valueGet ( ) { + + var i, retour = []; + + // Get the value from all handles. + for ( i = 0; i < options.handles; i += 1 ){ + retour[i] = options.format.to( $Values[i] ); + } + + return inSliderOrder( retour ); + } + + // Destroy the slider and unbind all events. + function destroyTarget ( ) { + + // Unbind events on the slider, remove all classes and child elements. + $(this).off(namespace) + .removeClass(Classes.join(' ')) + .empty(); + + delete this.LinkUpdate; + delete this.LinkConfirm; + delete this.LinkDefaultFormatter; + delete this.LinkDefaultFlag; + delete this.reappend; + delete this.vGet; + delete this.vSet; + delete this.getCurrentStep; + delete this.getInfo; + delete this.destroy; + + // Return the original options from the closure. + return originalOptions; + } + + // Get the current step size for the slider. + function getCurrentStep ( ) { + + // Check all locations, map them to their stepping point. + // Get the step point, then find it in the input list. + var retour = $.map($Locations, function( location, index ){ + + var step = $Spectrum.getApplicableStep( location ), + value = $Values[index], + increment = step[2], + decrement = (value - step[2]) >= step[1] ? step[2] : step[0]; + + return [[decrement, increment]]; + }); + + // Return values in the proper order. + return inSliderOrder( retour ); + } + + // Get the original set of options. + function getOriginalOptions ( ) { + return originalOptions; + } + + +// Initialize slider + + // Throw an error if the slider was already initialized. + if ( $Target.hasClass(Classes[0]) ) { + throw new Error('Slider was already initialized.'); + } + + // Create the base element, initialise HTML and set classes. + // Add handles and links. + $Base = addSlider( options.dir, options.ort, $Target ); + $Handles = addHandles( options.handles, options.dir, $Base ); + + // Set the connect classes. + addConnection ( options.connect, $Target, $Handles ); + + // Attach user events. + events( options.events ); + +// Methods + + target.vSet = valueSet; + target.vGet = valueGet; + target.destroy = destroyTarget; + + target.getCurrentStep = getCurrentStep; + target.getOriginalOptions = getOriginalOptions; + + target.getInfo = function(){ + return [ + $Spectrum, + options.style, + options.ort + ]; + }; + + // Use the public value method to set the start values. + $Target.val( options.start ); + +} + + + // Run the standard initializer + function initialize ( originalOptions ) { + + // Throw error if group is empty. + if ( !this.length ){ + throw new Error("noUiSlider: Can't initialize slider on empty selection."); + } + + // Test the options once, not for every slider. + var options = testOptions( originalOptions, this ); + + // Loop all items, and provide a new closed-scope environment. + return this.each(function(){ + closure(this, options, originalOptions); + }); + } + + // Destroy the slider, then re-enter initialization. + function rebuild ( options ) { + + return this.each(function(){ + + // The rebuild flag can be used if the slider wasn't initialized yet. + if ( !this.destroy ) { + $(this).noUiSlider( options ); + return; + } + + // Get the current values from the slider, + // including the initialization options. + var values = $(this).val(), originalOptions = this.destroy(), + + // Extend the previous options with the newly provided ones. + newOptions = $.extend( {}, originalOptions, options ); + + // Run the standard initializer. + $(this).noUiSlider( newOptions ); + + // Place Link elements back. + this.reappend(); + + // If the start option hasn't changed, + // reset the previous values. + if ( originalOptions.start === newOptions.start ) { + $(this).val(values); + } + }); + } + + // Access the internal getting and setting methods based on argument count. + function value ( ) { + return this[0][ !arguments.length ? 'vGet' : 'vSet' ].apply(this[0], arguments); + } + + // Override the .val() method. Test every element. Is it a slider? Go to + // the slider value handling. No? Use the standard method. + // Note how $.fn.val expects 'this' to be an instance of $. For convenience, + // the above 'value' function does too. + $.fn.val = function ( ) { + + // this === instanceof $ + + function valMethod( a ){ + return a.hasClass(Classes[0]) ? value : $val; + } + + var args = arguments, + first = $(this[0]); + + if ( !arguments.length ) { + return valMethod(first).call(first); + } + + // Return the set so it remains chainable + return this.each(function(){ + valMethod($(this)).apply($(this), args); + }); + }; + +// Extend jQuery/Zepto with the noUiSlider method. + $.fn.noUiSlider = function ( options, rebuildFlag ) { + + switch ( options ) { + case 'step': return this[0].getCurrentStep(); + case 'options': return this[0].getOriginalOptions(); + } + + return ( rebuildFlag ? rebuild : initialize ).call(this, options); + }; + + function getGroup ( $Spectrum, mode, values, stepped ) { + + // Use the range. + if ( mode === 'range' || mode === 'steps' ) { + return $Spectrum.xVal; + } + + if ( mode === 'count' ) { + + // Divide 0 - 100 in 'count' parts. + var spread = ( 100 / (values-1) ), v, i = 0; + values = []; + + // List these parts and have them handled as 'positions'. + while ((v=i++*spread) <= 100 ) { + values.push(v); + } + + mode = 'positions'; + } + + if ( mode === 'positions' ) { + + // Map all percentages to on-range values. + return $.map(values, function( value ){ + return $Spectrum.fromStepping( stepped ? $Spectrum.getStep( value ) : value ); + }); + } + + if ( mode === 'values' ) { + + // If the value must be stepped, it needs to be converted to a percentage first. + if ( stepped ) { + + return $.map(values, function( value ){ + + // Convert to percentage, apply step, return to value. + return $Spectrum.fromStepping( $Spectrum.getStep( $Spectrum.toStepping( value ) ) ); + }); + + } + + // Otherwise, we can simply use the values. + return values; + } + } + + function generateSpread ( $Spectrum, density, mode, group ) { + + var originalSpectrumDirection = $Spectrum.direction, + indexes = {}, + firstInRange = $Spectrum.xVal[0], + lastInRange = $Spectrum.xVal[$Spectrum.xVal.length-1], + ignoreFirst = false, + ignoreLast = false, + prevPct = 0; + + // This function loops the spectrum in an ltr linear fashion, + // while the toStepping method is direction aware. Trick it into + // believing it is ltr. + $Spectrum.direction = 0; + + // Create a copy of the group, sort it and filter away all duplicates. + group = unique(group.slice().sort(function(a, b){ return a - b; })); + + // Make sure the range starts with the first element. + if ( group[0] !== firstInRange ) { + group.unshift(firstInRange); + ignoreFirst = true; + } + + // Likewise for the last one. + if ( group[group.length - 1] !== lastInRange ) { + group.push(lastInRange); + ignoreLast = true; + } + + $.each(group, function ( index ) { + + // Get the current step and the lower + upper positions. + var step, i, q, + low = group[index], + high = group[index+1], + newPct, pctDifference, pctPos, type, + steps, realSteps, stepsize; + + // When using 'steps' mode, use the provided steps. + // Otherwise, we'll step on to the next subrange. + if ( mode === 'steps' ) { + step = $Spectrum.xNumSteps[ index ]; + } + + // Default to a 'full' step. + if ( !step ) { + step = high-low; + } + + // Low can be 0, so test for false. If high is undefined, + // we are at the last subrange. Index 0 is already handled. + if ( low === false || high === undefined ) { + return; + } + + // Find all steps in the subrange. + for ( i = low; i <= high; i += step ) { + + // Get the percentage value for the current step, + // calculate the size for the subrange. + newPct = $Spectrum.toStepping( i ); + pctDifference = newPct - prevPct; + + steps = pctDifference / density; + realSteps = Math.round(steps); + + // This ratio represents the ammount of percentage-space a point indicates. + // For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-devided. + // Round the percentage offset to an even number, then divide by two + // to spread the offset on both sides of the range. + stepsize = pctDifference/realSteps; + + // Divide all points evenly, adding the correct number to this subrange. + // Run up to <= so that 100% gets a point, event if ignoreLast is set. + for ( q = 1; q <= realSteps; q += 1 ) { + + // The ratio between the rounded value and the actual size might be ~1% off. + // Correct the percentage offset by the number of points + // per subrange. density = 1 will result in 100 points on the + // full range, 2 for 50, 4 for 25, etc. + pctPos = prevPct + ( q * stepsize ); + indexes[pctPos.toFixed(5)] = ['x', 0]; + } + + // Determine the point type. + type = ($.inArray(i, group) > -1) ? 1 : ( mode === 'steps' ? 2 : 0 ); + + // Enforce the 'ignoreFirst' option by overwriting the type for 0. + if ( !index && ignoreFirst && !low ) { + type = 0; + } + + if ( !(i === high && ignoreLast)) { + // Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value. + indexes[newPct.toFixed(5)] = [i, type]; + } + + // Update the percentage count. + prevPct = newPct; + } + }); + + // Reset the spectrum. + $Spectrum.direction = originalSpectrumDirection; + + return indexes; + } + + function addMarking ( CSSstyle, orientation, direction, spread, filterFunc, formatter ) { + + var style = ['horizontal', 'vertical'][orientation], + element = $('
'); + + element.addClass('noUi-pips noUi-pips-'+style); + + function getSize( type, value ){ + return [ '-normal', '-large', '-sub' ][(type&&filterFunc) ? filterFunc(value, type) : type]; + } + function getTags( offset, source, values ) { + return 'class="' + source + ' ' + + source + '-' + style + ' ' + + source + getSize(values[1], values[0]) + + '" style="' + CSSstyle + ': ' + offset + '%"'; + } + function addSpread ( offset, values ){ + + if ( direction ) { + offset = 100 - offset; + } + + // Add a marker for every point + element.append('
'); + + // Values are only appended for points marked '1' or '2'. + if ( values[1] ) { + element.append('
' + formatter.to(values[0]) + '
'); + } + } + + // Append all points. + $.each(spread, addSpread); + + return element; + } + + $.fn.noUiSlider_pips = function ( grid ) { + + var mode = grid.mode, + density = grid.density || 1, + filter = grid.filter || false, + values = grid.values || false, + format = grid.format || { + to: Math.round + }, + stepped = grid.stepped || false; + + return this.each(function(){ + + var info = this.getInfo(), + group = getGroup( info[0], mode, values, stepped ), + spread = generateSpread( info[0], density, mode, group ); + + return $(this).append(addMarking( + info[1], + info[2], + info[0].direction, + spread, + filter, + format + )); + }); + }; + +}( window.jQuery || window.Zepto )); diff --git a/distribute/jquery.nouislider.all.min.js b/distribute/jquery.nouislider.all.min.js new file mode 100644 index 00000000..d8fddadc --- /dev/null +++ b/distribute/jquery.nouislider.all.min.js @@ -0,0 +1,3 @@ +/*! noUiSlider - 7.0.3 - 2014-09-11 16:30:12 */ + +!function(){"use strict";function a(a){return a.split("").reverse().join("")}function b(a,b){return a.substring(0,b.length)===b}function c(a,b){return a.slice(-1*b.length)===b}function d(a,b,c){if((a[b]||a[c])&&a[b]===a[c])throw new Error(b)}function e(a){return"number"==typeof a&&isFinite(a)}function f(a,b){var c=Math.pow(10,b);return(Math.round(a*c)/c).toFixed(b)}function g(b,c,d,g,h,i,j,k,l,m,n,o){var p,q,r,s=o,t="",u="";return i&&(o=i(o)),e(o)?(b&&0===parseFloat(o.toFixed(b))&&(o=0),0>o&&(p=!0,o=Math.abs(o)),b!==!1&&(o=f(o,b)),o=o.toString(),-1!==o.indexOf(".")?(q=o.split("."),r=q[0],d&&(t=d+q[1])):r=o,c&&(r=a(r).match(/.{1,3}/g),r=a(r.join(a(c)))),p&&k&&(u+=k),g&&(u+=g),p&&l&&(u+=l),u+=r,u+=t,h&&(u+=h),m&&(u=m(u,s)),u):!1}function h(a,d,f,g,h,i,j,k,l,m,n,o){var p,q="";return n&&(o=n(o)),o&&"string"==typeof o?(k&&b(o,k)&&(o=o.replace(k,""),p=!0),g&&b(o,g)&&(o=o.replace(g,"")),l&&b(o,l)&&(o=o.replace(l,""),p=!0),h&&c(o,h)&&(o=o.slice(0,-1*h.length)),d&&(o=o.split(d).join("")),f&&(o=o.replace(f,".")),p&&(q+="-"),q+=o,q=q.replace(/[^0-9\.\-.]/g,""),""===q?!1:(q=Number(q),j&&(q=j(q)),e(q)?q:!1)):!1}function i(a){var b,c,e,f={};for(b=0;b=0&&8>e))throw new Error(c);f[c]=e}else if("encoder"===c||"decoder"===c||"edit"===c||"undo"===c){if("function"!=typeof e)throw new Error(c);f[c]=e}else{if("string"!=typeof e)throw new Error(c);f[c]=e}return d(f,"mark","thousand"),d(f,"prefix","negative"),d(f,"prefix","negativeBefore"),f}function j(a,b,c){var d,e=[];for(d=0;d"),!0):void 0}function d(b){if("string"==typeof b&&0!==b.indexOf("-")){this.method="val";var c=document.createElement("input");return c.name=b,c.type="hidden",this.target=this.el=a(c),!0}}function e(a){return"function"==typeof a?(this.target=!1,this.method=a,!0):void 0}function f(a,c){return b(a)&&!c?(a.is("input, select, textarea")?(this.method="val",this.target=a.on("change.liblink",this.changeHandler)):(this.target=a,this.method="html"),!0):void 0}function g(a,c){return b(a)&&("function"==typeof c||"string"==typeof c&&a[c])?(this.method=c,this.target=a,!0):void 0}function h(b,c,d){var e=this,f=!1;if(this.changeHandler=function(b){var c=e.formatInstance.from(a(this).val());return c===!1||isNaN(c)?(a(this).val(e.lastSetValue),!1):void e.changeHandlerMethod.call("",b,c)},this.el=!1,this.formatInstance=d,a.each(k,function(a,d){return f=d.call(e,b,c),!f}),!f)throw new RangeError("(Link) Invalid Link.")}function i(a){this.items=[],this.elements=[],this.origin=a}function j(b,c,d,e){0===b&&(b=this.LinkDefaultFlag),this.linkAPI||(this.linkAPI={}),this.linkAPI[b]||(this.linkAPI[b]=new i(this));var f=new h(c,d,e||this.LinkDefaultFormatter);f.target||(f.target=a(this)),f.changeHandlerMethod=this.LinkConfirm(b,f.el),this.linkAPI[b].push(f,f.el),this.LinkUpdate(b)}var k=[c,d,e,f,g];h.prototype.set=function(a){var b=Array.prototype.slice.call(arguments),c=b.slice(1);this.lastSetValue=this.formatInstance.to(a),c.unshift(this.lastSetValue),("function"==typeof this.method?this.method:this.target[this.method]).apply(this.target,c)},i.prototype.push=function(a,b){this.items.push(a),b&&this.elements.push(b)},i.prototype.reconfirm=function(a){var b;for(b=0;b=b[c];)c+=1;return c}function n(a,b,c){if(c>=a.slice(-1)[0])return 100;var d,e,f,g,h=m(c,a);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],f+k([d,e],c)/i(f,g)}function o(a,b,c){if(c>=100)return a.slice(-1)[0];var d,e,f,g,h=m(c,b);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],l([d,e],(c-f)*i(f,g))}function p(a,b,d,e){if(100===e)return e;var f,g,h=m(e,a);return d?(f=a[h-1],g=a[h],e-f>(g-f)/2?g:f):b[h-1]?a[h-1]+c(e-a[h-1],b[h-1]):e}function q(a,b,c){var e;if("number"==typeof b&&(b=[b]),"[object Array]"!==Object.prototype.toString.call(b))throw new Error("noUiSlider: 'range' contains invalid value.");if(e="min"===a?0:"max"===a?100:parseFloat(a),!d(e)||!d(b[0]))throw new Error("noUiSlider: 'range' value isn't numeric.");c.xPct.push(e),c.xVal.push(b[0]),e?c.xSteps.push(isNaN(b[1])?!1:b[1]):isNaN(b[1])||(c.xSteps[0]=b[1])}function r(a,b,c){return b?void(c.xSteps[a]=j([c.xVal[a],c.xVal[a+1]],b)/i(c.xPct[a],c.xPct[a+1])):!0}function s(a,b,c,d){this.xPct=[],this.xVal=[],this.xSteps=[d||!1],this.xNumSteps=[!1],this.snap=b,this.direction=c;var e,f=this;for(e in a)a.hasOwnProperty(e)&&q(e,a[e],f);f.xNumSteps=f.xSteps.slice(0);for(e in f.xNumSteps)f.xNumSteps.hasOwnProperty(e)&&r(Number(e),f.xNumSteps[e],f)}function t(a,b){if(!d(b))throw new Error("noUiSlider: 'step' is not numeric.");a.singleStep=b}function u(b,c){if("object"!=typeof c||a.isArray(c))throw new Error("noUiSlider: 'range' is not an object.");if(void 0===c.min||void 0===c.max)throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'.");b.spectrum=new s(c,b.snap,b.dir,b.singleStep)}function v(b,c){if(c=h(c),!a.isArray(c)||!c.length||c.length>2)throw new Error("noUiSlider: 'start' option is incorrect.");b.handles=c.length,b.start=c}function w(a,b){if(a.snap=b,"boolean"!=typeof b)throw new Error("noUiSlider: 'snap' option must be a boolean.")}function x(a,b){if(a.animate=b,"boolean"!=typeof b)throw new Error("noUiSlider: 'animate' option must be a boolean.")}function y(a,b){if("lower"===b&&1===a.handles)a.connect=1;else if("upper"===b&&1===a.handles)a.connect=2;else if(b===!0&&2===a.handles)a.connect=3;else{if(b!==!1)throw new Error("noUiSlider: 'connect' option doesn't match handle count.");a.connect=0}}function z(a,b){switch(b){case"horizontal":a.ort=0;break;case"vertical":a.ort=1;break;default:throw new Error("noUiSlider: 'orientation' option is invalid.")}}function A(a,b){if(!d(b))throw new Error("noUiSlider: 'margin' option must be numeric.");if(a.margin=a.spectrum.getMargin(b),!a.margin)throw new Error("noUiSlider: 'margin' option is only supported on linear sliders.")}function B(a,b){if(!d(b))throw new Error("noUiSlider: 'limit' option must be numeric.");if(a.limit=a.spectrum.getMargin(b),!a.limit)throw new Error("noUiSlider: 'limit' option is only supported on linear sliders.")}function C(a,b){switch(b){case"ltr":a.dir=0;break;case"rtl":a.dir=1,a.connect=[0,2,1,3][a.connect];break;default:throw new Error("noUiSlider: 'direction' option was not recognized.")}}function D(a,b){if("string"!=typeof b)throw new Error("noUiSlider: 'behaviour' must be a string containing options.");var c=b.indexOf("tap")>=0,d=b.indexOf("drag")>=0,e=b.indexOf("fixed")>=0,f=b.indexOf("snap")>=0;a.events={tap:c||f,drag:d,fixed:e,snap:f}}function E(a,b){if(a.format=b,"function"==typeof b.to&&"function"==typeof b.from)return!0;throw new Error("noUiSlider: 'format' requires 'to' and 'from' methods.")}function F(b){var c,d={margin:0,limit:0,animate:!0,format:Y};return c={step:{r:!1,t:t},start:{r:!0,t:v},connect:{r:!0,t:y},direction:{r:!0,t:C},snap:{r:!1,t:w},animate:{r:!1,t:x},range:{r:!0,t:u},orientation:{r:!1,t:z},margin:{r:!1,t:A},limit:{r:!1,t:B},behaviour:{r:!0,t:D},format:{r:!1,t:E}},b=a.extend({connect:!1,direction:"ltr",behaviour:"tap",orientation:"horizontal"},b),a.each(c,function(a,c){if(void 0===b[a]){if(c.r)throw new Error("noUiSlider: '"+a+"' is required.");return!0}c.t(d,b[a])}),d.style=d.ort?"top":"left",d}function G(a,b,c){var d=a+b[0],e=a+b[1];return c?(0>d&&(e+=Math.abs(d)),e>100&&(d-=e-100),[g(d),g(e)]):[d,e]}function H(a){a.preventDefault();var b,c,d=0===a.type.indexOf("touch"),e=0===a.type.indexOf("mouse"),f=0===a.type.indexOf("pointer"),g=a;return 0===a.type.indexOf("MSPointer")&&(f=!0),a.originalEvent&&(a=a.originalEvent),d&&(b=a.changedTouches[0].pageX,c=a.changedTouches[0].pageY),(e||f)&&(f||void 0!==window.pageXOffset||(window.pageXOffset=document.documentElement.scrollLeft,window.pageYOffset=document.documentElement.scrollTop),b=a.clientX+window.pageXOffset,c=a.clientY+window.pageYOffset),g.points=[b,c],g.cursor=e,g}function I(b,c){var d=a("
").addClass(X[2]),e=["-lower","-upper"];return b&&e.reverse(),d.children().addClass(X[3]+" "+X[3]+e[c]),d}function J(a,b,c){switch(a){case 1:b.addClass(X[7]),c[0].addClass(X[6]);break;case 3:c[1].addClass(X[6]);case 2:c[0].addClass(X[7]);case 0:b.addClass(X[6])}}function K(a,b,c){var d,e=[];for(d=0;a>d;d+=1)e.push(I(b,d).appendTo(c));return e}function L(b,c,d){return d.addClass([X[0],X[8+b],X[4+c]].join(" ")),a("
").appendTo(d).addClass(X[1])}function M(b,c,d){function e(){return B[["width","height"][c.ort]]()}function i(a){var b,c=[D.val()];for(b=0;b1&&(e=1===e?0:1),k(e)}function n(){var a,b;for(a=0;a1),f=u(d[0],c[h],1===d.length),d.length>1&&(f=u(d[1],c[h?0:1],!1)||f),f&&i(["slide"])}function q(b){a("."+X[15]).removeClass(X[15]),b.cursor&&a("body").css("cursor","").off(V),T.off(V),D.removeClass(X[12]),i(["set","change"])}function r(b,c){1===c.handles.length&&c.handles[0].children().addClass(X[15]),b.stopPropagation(),o(W.move,T,p,{start:b.calcPoint,handles:c.handles,positions:[E[0],E[C.length-1]]}),o(W.end,T,q,null),b.cursor&&(a("body").css("cursor",a(b.target).css("cursor")),C.length>1&&D.addClass(X[12]),a("body").on("selectstart"+V,!1))}function s(b){var d,g=b.calcPoint,h=0;b.stopPropagation(),a.each(C,function(){h+=this.offset()[c.style]}),h=h/2>g||1===C.length?0:1,g-=B.offset()[c.style],d=100*g/e(),c.events.snap||f(D,X[14],300),u(C[h],d),i(["slide","set","change"]),c.events.snap&&r(b,{handles:[C[h]]})}function t(a){var b,c;if(!a.fixed)for(b=0;b1&&(b=e?Math.max(b,f):Math.min(b,h)),d!==!1&&c.limit&&C.length>1&&(b=e?Math.min(b,i):Math.max(b,j)),b=F.getStep(b),b=g(parseFloat(b.toFixed(7))),b===E[e]?!1:(a.css(c.style,b+"%"),a.is(":first-child")&&a.toggleClass(X[17],b>50),E[e]=b,I[e]=F.fromStepping(b),l(M[e]),!0)}function v(a,b){var d,e,f;for(c.limit&&(a+=1),d=0;a>d;d+=1)e=d%2,f=b[e],null!==f&&f!==!1&&("number"==typeof f&&(f=String(f)),f=c.format.from(f),(f===!1||isNaN(f)||u(C[e],F.toStepping(f),d===3-c.dir)===!1)&&l(M[e]))}function w(a){if(D[0].LinkIsEmitting)return this;var b,d=h(a);return c.dir&&c.handles>1&&d.reverse(),c.animate&&-1!==E[0]&&f(D,X[14],300),b=C.length>1?3:1,1===d.length&&(b=1),v(b,d),i(["set"]),this}function x(){var a,b=[];for(a=0;a=c[1]?c[2]:c[0];return[[f,e]]});return j(b)}function A(){return d}var B,C,D=a(b),E=[-1,-1],F=c.spectrum,I=[],M=["lower","upper"].slice(0,c.handles);if(c.dir&&M.reverse(),b.LinkUpdate=l,b.LinkConfirm=m,b.LinkDefaultFormatter=c.format,b.LinkDefaultFlag="lower",b.reappend=n,D.hasClass(X[0]))throw new Error("Slider was already initialized.");B=L(c.dir,c.ort,D),C=K(c.handles,c.dir,B),J(c.connect,D,C),t(c.events),b.vSet=w,b.vGet=x,b.destroy=y,b.getCurrentStep=z,b.getOriginalOptions=A,b.getInfo=function(){return[F,c.style,c.ort]},D.val(c.start)}function N(a){if(!this.length)throw new Error("noUiSlider: Can't initialize slider on empty selection.");var b=F(a,this);return this.each(function(){M(this,b,a)})}function O(b){return this.each(function(){if(!this.destroy)return void a(this).noUiSlider(b);var c=a(this).val(),d=this.destroy(),e=a.extend({},d,b);a(this).noUiSlider(e),this.reappend(),d.start===e.start&&a(this).val(c)})}function P(){return this[0][arguments.length?"vSet":"vGet"].apply(this[0],arguments)}function Q(b,c,d,e){if("range"===c||"steps"===c)return b.xVal;if("count"===c){var f,g=100/(d-1),h=0;for(d=[];(f=h++*g)<=100;)d.push(f);c="positions"}return"positions"===c?a.map(d,function(a){return b.fromStepping(e?b.getStep(a):a)}):"values"===c?e?a.map(d,function(a){return b.fromStepping(b.getStep(b.toStepping(a)))}):d:void 0}function R(c,d,e,f){var g=c.direction,h={},i=c.xVal[0],j=c.xVal[c.xVal.length-1],k=!1,l=!1,m=0;return c.direction=0,f=b(f.slice().sort(function(a,b){return a-b})),f[0]!==i&&(f.unshift(i),k=!0),f[f.length-1]!==j&&(f.push(j),l=!0),a.each(f,function(b){var g,i,j,n,o,p,q,r,s,t,u=f[b],v=f[b+1];if("steps"===e&&(g=c.xNumSteps[b]),g||(g=v-u),u!==!1&&void 0!==v)for(i=u;v>=i;i+=g){for(n=c.toStepping(i),o=n-m,r=o/d,s=Math.round(r),t=o/s,j=1;s>=j;j+=1)p=m+j*t,h[p.toFixed(5)]=["x",0];q=a.inArray(i,f)>-1?1:"steps"===e?2:0,b||!k||u||(q=0),i===v&&l||(h[n.toFixed(5)]=[i,q]),m=n}}),c.direction=g,h}function S(b,c,d,e,f,g){function h(a,b){return["-normal","-large","-sub"][a&&f?f(b,a):a]}function i(a,c,d){return'class="'+c+" "+c+"-"+k+" "+c+h(d[1],d[0])+'" style="'+b+": "+a+'%"'}function j(a,b){d&&(a=100-a),l.append("
"),b[1]&&l.append("
"+g.to(b[0])+"
")}var k=["horizontal","vertical"][c],l=a("
");return l.addClass("noUi-pips noUi-pips-"+k),a.each(e,j),l}var T=a(document),U=a.fn.val,V=".nui",W=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"},X=["noUi-target","noUi-base","noUi-origin","noUi-handle","noUi-horizontal","noUi-vertical","noUi-background","noUi-connect","noUi-ltr","noUi-rtl","noUi-dragable","","noUi-state-drag","","noUi-state-tap","noUi-active","","noUi-stacking"];s.prototype.getMargin=function(a){return 2===this.xPct.length?j(this.xVal,a):!1},s.prototype.toStepping=function(a){return a=n(this.xVal,this.xPct,a),this.direction&&(a=100-a),a},s.prototype.fromStepping=function(a){return this.direction&&(a=100-a),e(o(this.xVal,this.xPct,a))},s.prototype.getStep=function(a){return this.direction&&(a=100-a),a=p(this.xPct,this.xSteps,this.snap,a),this.direction&&(a=100-a),a},s.prototype.getApplicableStep=function(a){var b=m(a,this.xPct),c=100===a?2:1;return[this.xNumSteps[b-2],this.xVal[b-c],this.xNumSteps[b-c]]},s.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var Y={to:function(a){return a.toFixed(2)},from:Number};a.fn.val=function(){function b(a){return a.hasClass(X[0])?P:U}var c=arguments,d=a(this[0]);return arguments.length?this.each(function(){b(a(this)).apply(a(this),c)}):b(d).call(d)},a.fn.noUiSlider=function(a,b){switch(a){case"step":return this[0].getCurrentStep();case"options":return this[0].getOriginalOptions()}return(b?O:N).call(this,a)},a.fn.noUiSlider_pips=function(b){var c=b.mode,d=b.density||1,e=b.filter||!1,f=b.values||!1,g=b.format||{to:Math.round},h=b.stepped||!1;return this.each(function(){var b=this.getInfo(),i=Q(b[0],c,f,h),j=R(b[0],d,c,i);return a(this).append(S(b[1],b[2],b[0].direction,j,e,g))})}}(window.jQuery||window.Zepto); \ No newline at end of file diff --git a/distribute/jquery.nouislider.js b/distribute/jquery.nouislider.js new file mode 100644 index 00000000..a317f60f --- /dev/null +++ b/distribute/jquery.nouislider.js @@ -0,0 +1,1406 @@ +/*! noUiSlider - 7.0.3 - 2014-09-11 16:30:11 */ + +/*jslint browser: true */ +/*jslint white: true */ + +(function( $ ){ + + 'use strict'; + + + // Removes duplicates from an array. + function unique(array) { + return $.grep(array, function(el, index) { + return index === $.inArray(el, array); + }); + } + + // Round a value to the closest 'to'. + function closest ( value, to ) { + return Math.round(value / to) * to; + } + + // Checks whether a value is numerical. + function isNumeric ( a ) { + return typeof a === 'number' && !isNaN( a ) && isFinite( a ); + } + + // Rounds a number to 7 supported decimals. + function accurateNumber( number ) { + var p = Math.pow(10, 7); + return Number((Math.round(number*p)/p).toFixed(7)); + } + + // Sets a class and removes it after [duration] ms. + function addClassFor ( element, className, duration ) { + element.addClass(className); + setTimeout(function(){ + element.removeClass(className); + }, duration); + } + + // Limits a value to 0 - 100 + function limit ( a ) { + return Math.max(Math.min(a, 100), 0); + } + + // Wraps a variable as an array, if it isn't one yet. + function asArray ( a ) { + return $.isArray(a) ? a : [a]; + } + + + var + // Cache the document selector; + /** @const */ + doc = $(document), + // Make a backup of the original jQuery/Zepto .val() method. + /** @const */ + $val = $.fn.val, + // Namespace for binding and unbinding slider events; + /** @const */ + namespace = '.nui', + // Determine the events to bind. IE11 implements pointerEvents without + // a prefix, which breaks compatibility with the IE10 implementation. + /** @const */ + 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' + }, + // Re-usable list of classes; + /** @const */ + Classes = [ +/* 0 */ 'noUi-target' +/* 1 */ ,'noUi-base' +/* 2 */ ,'noUi-origin' +/* 3 */ ,'noUi-handle' +/* 4 */ ,'noUi-horizontal' +/* 5 */ ,'noUi-vertical' +/* 6 */ ,'noUi-background' +/* 7 */ ,'noUi-connect' +/* 8 */ ,'noUi-ltr' +/* 9 */ ,'noUi-rtl' +/* 10 */ ,'noUi-dragable' +/* 11 */ ,'' +/* 12 */ ,'noUi-state-drag' +/* 13 */ ,'' +/* 14 */ ,'noUi-state-tap' +/* 15 */ ,'noUi-active' +/* 16 */ ,'' +/* 17 */ ,'noUi-stacking' + ]; + + +// Value calculation + + // Determine the size of a sub-range in relation to a full range. + function subRangeRatio ( pa, pb ) { + return (100 / (pb - pa)); + } + + // (percentage) How many percent is this value of this range? + function fromPercentage ( range, value ) { + return (value * 100) / ( range[1] - range[0] ); + } + + // (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]; + } + + +// Range conversion + + function getJ ( value, arr ) { + + var j = 1; + + while ( value >= arr[j] ){ + j += 1; + } + + return j; + } + + // (percentage) Input a value, find where, on a scale of 0-100, it applies. + function toStepping ( xVal, xPct, value ) { + + if ( value >= xVal.slice(-1)[0] ){ + return 100; + } + + var j = getJ( value, xVal ), va, vb, pa, pb; + + va = xVal[j-1]; + vb = xVal[j]; + pa = xPct[j-1]; + pb = xPct[j]; + + return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb)); + } + + // (value) Input a percentage, find where it is on the specified range. + function fromStepping ( xVal, xPct, value ) { + + // There is no range group that fits 100 + if ( value >= 100 ){ + return xVal.slice(-1)[0]; + } + + var j = getJ( value, xPct ), va, vb, pa, pb; + + va = xVal[j-1]; + vb = xVal[j]; + pa = xPct[j-1]; + pb = xPct[j]; + + return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb)); + } + + // (percentage) Get the step that applies at a certain value. + function getStep ( xPct, xSteps, snap, value ) { + + if ( value === 100 ) { + return value; + } + + var j = getJ( value, xPct ), a, b; + + // If 'snap' is set, steps are used as fixed points on the slider. + if ( snap ) { + + a = xPct[j-1]; + b = xPct[j]; + + // Find the closest position, a or b. + if ((value - a) > ((b-a)/2)){ + return b; + } + + return a; + } + + if ( !xSteps[j-1] ){ + return value; + } + + return xPct[j-1] + closest( + value - xPct[j-1], + xSteps[j-1] + ); + } + + +// Entry parsing + + function handleEntryPoint ( index, value, that ) { + + var percentage; + + // Wrap numerical input in an array. + if ( typeof value === "number" ) { + value = [value]; + } + + // Reject any invalid input, by testing whether value is an array. + if ( Object.prototype.toString.call( value ) !== '[object Array]' ){ + throw new Error("noUiSlider: 'range' contains invalid value."); + } + + // Covert min/max syntax to 0 and 100. + if ( index === 'min' ) { + percentage = 0; + } else if ( index === 'max' ) { + percentage = 100; + } else { + percentage = parseFloat( index ); + } + + // Check for correct input. + if ( !isNumeric( percentage ) || !isNumeric( value[0] ) ) { + throw new Error("noUiSlider: 'range' value isn't numeric."); + } + + // Store values. + that.xPct.push( percentage ); + that.xVal.push( value[0] ); + + // NaN will evaluate to false too, but to keep + // logging clear, set step explicitly. Make sure + // not to override the 'step' setting with false. + if ( !percentage ) { + if ( !isNaN( value[1] ) ) { + that.xSteps[0] = value[1]; + } + } else { + that.xSteps.push( isNaN(value[1]) ? false : value[1] ); + } + } + + function handleStepPoint ( i, n, that ) { + + // Ignore 'false' stepping. + if ( !n ) { + return true; + } + + // Factor to range ratio + that.xSteps[i] = fromPercentage([ + that.xVal[i] + ,that.xVal[i+1] + ], n) / subRangeRatio ( + that.xPct[i], + that.xPct[i+1] ); + } + + +// Interface + + // The interface to Spectrum handles all direction-based + // conversions, so the above values are unaware. + + function Spectrum ( entry, snap, direction, singleStep ) { + + this.xPct = []; + this.xVal = []; + this.xSteps = [ singleStep || false ]; + this.xNumSteps = [ false ]; + + this.snap = snap; + this.direction = direction; + + var that = this, index; + + // Loop all entries. + for ( index in entry ) { + if ( entry.hasOwnProperty(index) ) { + handleEntryPoint(index, entry[index], that); + } + } + + // Store the actual step values. + that.xNumSteps = that.xSteps.slice(0); + + for ( index in that.xNumSteps ) { + if ( that.xNumSteps.hasOwnProperty(index) ) { + handleStepPoint(Number(index), that.xNumSteps[index], that); + } + } + } + + Spectrum.prototype.getMargin = function ( value ) { + return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false; + }; + + Spectrum.prototype.toStepping = function ( value ) { + + value = toStepping( this.xVal, this.xPct, value ); + + // Invert the value if this is a right-to-left slider. + if ( this.direction ) { + value = 100 - value; + } + + return value; + }; + + Spectrum.prototype.fromStepping = function ( value ) { + + // Invert the value if this is a right-to-left slider. + if ( this.direction ) { + value = 100 - value; + } + + return accurateNumber(fromStepping( this.xVal, this.xPct, value )); + }; + + Spectrum.prototype.getStep = function ( value ) { + + // Find the proper step for rtl sliders by search in inverse direction. + // Fixes issue #262. + if ( this.direction ) { + value = 100 - value; + } + + value = getStep(this.xPct, this.xSteps, this.snap, value ); + + if ( this.direction ) { + value = 100 - value; + } + + return value; + }; + + Spectrum.prototype.getApplicableStep = function ( value ) { + + // If the value is 100%, return the negative step twice. + var j = getJ(value, this.xPct), offset = value === 100 ? 2 : 1; + return [this.xNumSteps[j-2], this.xVal[j-offset], this.xNumSteps[j-offset]]; + }; + + // Outside testing + Spectrum.prototype.convert = function ( value ) { + return this.getStep(this.toStepping(value)); + }; + +/* 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. */ + + /** @const */ + var defaultFormatter = { 'to': function( value ){ + return value.toFixed(2); + }, 'from': Number }; + + function testStep ( parsed, entry ) { + + if ( !isNumeric( entry ) ) { + throw new Error("noUiSlider: 'step' is not numeric."); + } + + // The step option can still be used to set stepping + // for linear sliders. Overwritten if set in 'range'. + parsed.singleStep = entry; + } + + function testRange ( parsed, entry ) { + + // Filter incorrect input. + if ( typeof entry !== 'object' || $.isArray(entry) ) { + throw new Error("noUiSlider: 'range' is not an object."); + } + + // Catch missing start or end. + if ( entry.min === undefined || entry.max === undefined ) { + throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'."); + } + + parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.dir, parsed.singleStep); + } + + function testStart ( parsed, entry ) { + + entry = asArray(entry); + + // Validate input. Values aren't tested, as the public .val method + // will always provide a valid location. + if ( !$.isArray( entry ) || !entry.length || entry.length > 2 ) { + throw new Error("noUiSlider: 'start' option is incorrect."); + } + + // Store the number of handles. + parsed.handles = entry.length; + + // When the slider is initialized, the .val method will + // be called with the start options. + parsed.start = entry; + } + + function testSnap ( parsed, entry ) { + + // Enforce 100% stepping within subranges. + parsed.snap = entry; + + if ( typeof entry !== 'boolean' ){ + throw new Error("noUiSlider: 'snap' option must be a boolean."); + } + } + + function testAnimate ( parsed, entry ) { + + // Enforce 100% stepping within subranges. + parsed.animate = entry; + + if ( typeof entry !== 'boolean' ){ + throw new Error("noUiSlider: 'animate' option must be a boolean."); + } + } + + function testConnect ( parsed, entry ) { + + if ( entry === 'lower' && parsed.handles === 1 ) { + parsed.connect = 1; + } else if ( entry === 'upper' && parsed.handles === 1 ) { + parsed.connect = 2; + } else if ( entry === true && parsed.handles === 2 ) { + parsed.connect = 3; + } else if ( entry === false ) { + parsed.connect = 0; + } else { + throw new Error("noUiSlider: 'connect' option doesn't match handle count."); + } + } + + function testOrientation ( parsed, entry ) { + + // Set orientation to an a numerical value for easy + // array selection. + switch ( entry ){ + case 'horizontal': + parsed.ort = 0; + break; + case 'vertical': + parsed.ort = 1; + break; + default: + throw new Error("noUiSlider: 'orientation' option is invalid."); + } + } + + function testMargin ( parsed, entry ) { + + if ( !isNumeric(entry) ){ + throw new Error("noUiSlider: 'margin' option must be numeric."); + } + + parsed.margin = parsed.spectrum.getMargin(entry); + + if ( !parsed.margin ) { + throw new Error("noUiSlider: 'margin' option is only supported on linear sliders."); + } + } + + function testLimit ( parsed, entry ) { + + if ( !isNumeric(entry) ){ + throw new Error("noUiSlider: 'limit' option must be numeric."); + } + + parsed.limit = parsed.spectrum.getMargin(entry); + + if ( !parsed.limit ) { + throw new Error("noUiSlider: 'limit' option is only supported on linear sliders."); + } + } + + function testDirection ( parsed, entry ) { + + // Set direction as a numerical value for easy parsing. + // Invert connection for RTL sliders, so that the proper + // handles get the connect/background classes. + switch ( entry ) { + case 'ltr': + parsed.dir = 0; + break; + case 'rtl': + parsed.dir = 1; + parsed.connect = [0,2,1,3][parsed.connect]; + break; + default: + throw new Error("noUiSlider: 'direction' option was not recognized."); + } + } + + function testBehaviour ( parsed, entry ) { + + // Make sure the input is a string. + if ( typeof entry !== 'string' ) { + throw new Error("noUiSlider: 'behaviour' must be a string containing options."); + } + + // Check if the string contains any keywords. + // None are required. + var tap = entry.indexOf('tap') >= 0, + drag = entry.indexOf('drag') >= 0, + fixed = entry.indexOf('fixed') >= 0, + snap = entry.indexOf('snap') >= 0; + + parsed.events = { + tap: tap || snap, + drag: drag, + fixed: fixed, + snap: snap + }; + } + + function testFormat ( parsed, entry ) { + + parsed.format = entry; + + // Any object with a to and from method is supported. + if ( typeof entry.to === 'function' && typeof entry.from === 'function' ) { + return true; + } + + throw new Error( "noUiSlider: 'format' requires 'to' and 'from' methods."); + } + + // Test all developer settings and parse to assumption-safe values. + function testOptions ( options ) { + + var parsed = { + margin: 0, + limit: 0, + animate: true, + format: defaultFormatter + }, tests; + + // Tests are executed in the order they are presented here. + tests = { + 'step': { r: false, t: testStep }, + 'start': { r: true, t: testStart }, + 'connect': { r: true, t: testConnect }, + 'direction': { r: true, t: testDirection }, + 'snap': { r: false, t: testSnap }, + 'animate': { r: false, t: testAnimate }, + 'range': { r: true, t: testRange }, + 'orientation': { r: false, t: testOrientation }, + 'margin': { r: false, t: testMargin }, + 'limit': { r: false, t: testLimit }, + 'behaviour': { r: true, t: testBehaviour }, + 'format': { r: false, t: testFormat } + }; + + // Set defaults where applicable. + options = $.extend({ + 'connect': false, + 'direction': 'ltr', + 'behaviour': 'tap', + 'orientation': 'horizontal' + }, options); + + // Run all options through a testing mechanism to ensure correct + // input. It should be noted that options might get modified to + // be handled properly. E.g. wrapping integers in arrays. + $.each( tests, function( name, test ){ + + // If the option isn't set, but it is required, throw an error. + if ( options[name] === undefined ) { + + if ( test.r ) { + throw new Error("noUiSlider: '" + name + "' is required."); + } + + return true; + } + + test.t( parsed, options[name] ); + }); + + // Pre-define the styles. + parsed.style = parsed.ort ? 'top' : 'left'; + + return parsed; + } + +// Class handling + + // Delimit proposed values for handle positions. + function getPositions ( a, b, delimit ) { + + // Add movement to current position. + var c = a + b[0], d = a + b[1]; + + // Only alter the other position on drag, + // not on standard sliding. + if ( delimit ) { + if ( c < 0 ) { + d += Math.abs(c); + } + if ( d > 100 ) { + c -= ( d - 100 ); + } + + // Limit values to 0 and 100. + return [limit(c), limit(d)]; + } + + return [c,d]; + } + + +// Event handling + + // Provide a clean event with standardized offset values. + function fixEvent ( e ) { + + // 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. Offset changes need to be + // made on an event specific basis. + var touch = e.type.indexOf('touch') === 0 + ,mouse = e.type.indexOf('mouse') === 0 + ,pointer = e.type.indexOf('pointer') === 0 + ,x,y, event = e; + + // IE10 implemented pointer events with a prefix; + if ( e.type.indexOf('MSPointer') === 0 ) { + pointer = true; + } + + // Get the originalEvent, if the event has been wrapped + // by jQuery. Zepto doesn't wrap the event. + if ( e.originalEvent ) { + e = e.originalEvent; + } + + if ( touch ) { + // noUiSlider supports one movement at a time, + // so we can select the first 'changedTouch'. + x = e.changedTouches[0].pageX; + y = e.changedTouches[0].pageY; + } + + if ( mouse || pointer ) { + + // Polyfill the pageXOffset and pageYOffset + // variables for IE7 and IE8; + if( !pointer && window.pageXOffset === undefined ){ + window.pageXOffset = document.documentElement.scrollLeft; + window.pageYOffset = document.documentElement.scrollTop; + } + + x = e.clientX + window.pageXOffset; + y = e.clientY + window.pageYOffset; + } + + event.points = [x, y]; + event.cursor = mouse; + + return event; + } + + +// DOM additions + + // Append a handle to the base. + function addHandle ( direction, index ) { + + var handle = $('
').addClass( Classes[2] ), + additions = [ '-lower', '-upper' ]; + + if ( direction ) { + additions.reverse(); + } + + handle.children().addClass( + Classes[3] + " " + Classes[3]+additions[index] + ); + + return handle; + } + + // Add the proper connection classes. + function addConnection ( connect, target, handles ) { + + // 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 ( connect ) { + case 1: target.addClass( Classes[7] ); + handles[0].addClass( Classes[6] ); + break; + case 3: handles[1].addClass( Classes[6] ); + /* falls through */ + case 2: handles[0].addClass( Classes[7] ); + /* falls through */ + case 0: target.addClass(Classes[6]); + break; + } + } + + // Add handles to the slider base. + function addHandles ( nrHandles, direction, base ) { + + var index, handles = []; + + // Append handles. + for ( index = 0; index < nrHandles; index += 1 ) { + + // Keep a list of all added handles. + handles.push( addHandle( direction, index ).appendTo(base) ); + } + + return handles; + } + + // Initialize a single slider. + function addSlider ( direction, orientation, target ) { + + // Apply classes and data to the target. + target.addClass([ + Classes[0], + Classes[8 + direction], + Classes[4 + orientation] + ].join(' ')); + + return $('
').appendTo(target).addClass( Classes[1] ); + } + +function closure ( target, options, originalOptions ){ + +// Internal variables + + // All variables local to 'closure' are marked $. + var $Target = $(target), + $Locations = [-1, -1], + $Base, + $Handles, + $Spectrum = options.spectrum, + $Values = [], + // libLink. For rtl sliders, 'lower' and 'upper' should not be inverted + // for one-handle sliders, so trim 'upper' it that case. + triggerPos = ['lower', 'upper'].slice(0, options.handles); + + // Invert the libLink connection for rtl sliders. + if ( options.dir ) { + triggerPos.reverse(); + } + +// Helpers + + // Shorthand for base dimensions. + function baseSize ( ) { + return $Base[['width', 'height'][options.ort]](); + } + + // External event handling + function fireEvents ( events ) { + + // Use the external api to get the values. + // Wrap the values in an array, as .trigger takes + // only one additional argument. + var index, values = [ $Target.val() ]; + + for ( index = 0; index < events.length; index += 1 ){ + $Target.trigger(events[index], values); + } + } + + // Returns the input array, respecting the slider direction configuration. + function inSliderOrder ( values ) { + + // If only one handle is used, return a single value. + if ( values.length === 1 ){ + return values[0]; + } + + if ( options.dir ) { + return values.reverse(); + } + + return values; + } + +// libLink integration + + // Create a new function which calls .val on input change. + function createChangeHandler ( trigger ) { + return function ( ignore, value ){ + // Determine which array position to 'null' based on 'trigger'. + $Target.val( [ trigger ? null : value, trigger ? value : null ], true ); + }; + } + + // Called by libLink when it wants a set of links updated. + function linkUpdate ( flag ) { + + var trigger = $.inArray(flag, triggerPos); + + // The API might not have been set yet. + if ( $Target[0].linkAPI && $Target[0].linkAPI[flag] ) { + $Target[0].linkAPI[flag].change( + $Values[trigger], + $Handles[trigger].children(), + $Target + ); + } + } + + // Called by libLink to append an element to the slider. + function linkConfirm ( flag, element ) { + + // Find the trigger for the passed flag. + var trigger = $.inArray(flag, triggerPos); + + // If set, append the element to the handle it belongs to. + if ( element ) { + element.appendTo( $Handles[trigger].children() ); + } + + // The public API is reversed for rtl sliders, so the changeHandler + // should not be aware of the inverted trigger positions. + // On rtl slider with one handle, 'lower' should be used. + if ( options.dir && options.handles > 1 ) { + trigger = trigger === 1 ? 0 : 1; + } + + return createChangeHandler( trigger ); + } + + // Place elements back on the slider. + function reAppendLink ( ) { + + var i, flag; + + // The API keeps a list of elements: we can re-append them on rebuild. + for ( i = 0; i < triggerPos.length; i += 1 ) { + if ( this.linkAPI && this.linkAPI[(flag = triggerPos[i])] ) { + this.linkAPI[flag].reconfirm(flag); + } + } + } + + target.LinkUpdate = linkUpdate; + target.LinkConfirm = linkConfirm; + target.LinkDefaultFormatter = options.format; + target.LinkDefaultFlag = 'lower'; + + target.reappend = reAppendLink; + + + // Handler for attaching events trough a proxy. + function attach ( events, element, callback, data ) { + + // This function can be used to 'filter' events to the slider. + + // Add the noUiSlider namespace to all events. + events = events.replace( /\s/g, namespace + ' ' ) + namespace; + + // Bind a closure on the target. + return element.on( events, function( e ){ + + // jQuery and Zepto (1) handle unset attributes differently, + // but always falsy; #208 + if ( !!$Target.attr('disabled') ) { + return false; + } + + // Stop if an active 'tap' transition is taking place. + if ( $Target.hasClass( Classes[14] ) ) { + return false; + } + + e = fixEvent(e); + e.calcPoint = e.points[ options.ort ]; + + // Call the event handler with the event [ and additional data ]. + callback ( e, data ); + }); + } + + // Handle movement on document for handle and range drag. + function move ( event, data ) { + + var handles = data.handles || $Handles, positions, state = false, + proposal = ((event.calcPoint - data.start) * 100) / baseSize(), + h = handles[0][0] !== $Handles[0][0] ? 1 : 0; + + // Calculate relative positions for the handles. + positions = getPositions( proposal, data.positions, handles.length > 1); + + state = setHandle ( handles[0], positions[h], handles.length === 1 ); + + if ( handles.length > 1 ) { + state = setHandle ( handles[1], positions[h?0:1], false ) || state; + } + + // Fire the 'slide' event if any handle moved. + if ( state ) { + fireEvents(['slide']); + } + } + + // Unbind move events on document, call callbacks. + function end ( event ) { + + // The handle is no longer active, so remove the class. + $('.' + Classes[15]).removeClass(Classes[15]); + + // Remove cursor styles and text-selection events bound to the body. + if ( event.cursor ) { + $('body').css('cursor', '').off( namespace ); + } + + // Unbind the move and end events, which are added on 'start'. + doc.off( namespace ); + + // Remove dragging class. + $Target.removeClass(Classes[12]); + + // Fire the change and set events. + fireEvents(['set', 'change']); + } + + // Bind move events on document. + function start ( event, data ) { + + // Mark the handle as 'active' so it can be styled. + if( data.handles.length === 1 ) { + data.handles[0].children().addClass(Classes[15]); + } + + // A drag should never propagate up to the 'tap' event. + event.stopPropagation(); + + // Attach the move event. + attach ( actions.move, doc, move, { + start: event.calcPoint, + handles: data.handles, + positions: [ + $Locations[0], + $Locations[$Handles.length - 1] + ] + }); + + // Unbind all movement when the drag ends. + attach ( actions.end, doc, end, null ); + + // Text selection isn't an issue on touch devices, + // so adding cursor styles can be skipped. + 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 ( $Handles.length > 1 ) { + $Target.addClass(Classes[12]); + } + + // Prevent text selection when dragging the handles. + $('body').on('selectstart' + namespace, false); + } + } + + // Move closest handle to tapped location. + function tap ( event ) { + + var location = event.calcPoint, total = 0, to; + + // The tap event shouldn't propagate up and cause 'edge' to run. + event.stopPropagation(); + + // Add up the handle offsets. + $.each( $Handles, function(){ + total += this.offset()[ options.style ]; + }); + + // Find the handle closest to the tapped position. + total = ( location < total/2 || $Handles.length === 1 ) ? 0 : 1; + + location -= $Base.offset()[ options.style ]; + + // Calculate the new position. + to = ( location * 100 ) / baseSize(); + + if ( !options.events.snap ) { + // Flag the slider as it is now in a transitional state. + // Transition takes 300 ms, so re-enable the slider afterwards. + addClassFor( $Target, Classes[14], 300 ); + } + + // Find the closest handle and calculate the tapped point. + // The set handle to the new position. + setHandle( $Handles[total], to ); + + fireEvents(['slide', 'set', 'change']); + + if ( options.events.snap ) { + start(event, { handles: [$Handles[total]] }); + } + } + + // Attach events to several slider parts. + function events ( behaviour ) { + + var i, drag; + + // Attach the standard drag event to the handles. + if ( !behaviour.fixed ) { + + for ( i = 0; i < $Handles.length; i += 1 ) { + + // These events are only bound to the visual handle + // element, not the 'real' origin element. + attach ( actions.start, $Handles[i].children(), start, { + handles: [ $Handles[i] ] + }); + } + } + + // Attach the tap event to the slider base. + if ( behaviour.tap ) { + + attach ( actions.start, $Base, tap, { + handles: $Handles + }); + } + + // Make the range dragable. + if ( behaviour.drag ){ + + drag = $Base.find( '.' + Classes[7] ).addClass( Classes[10] ); + + // 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 ( behaviour.fixed ) { + drag = drag.add($Base.children().not( drag ).children()); + } + + attach ( actions.start, drag, start, { + handles: $Handles + }); + } + } + + + // Test suggested values and apply margin, step. + function setHandle ( handle, to, noLimitOption ) { + + var trigger = handle[0] !== $Handles[0][0] ? 1 : 0, + lowerMargin = $Locations[0] + options.margin, + upperMargin = $Locations[1] - options.margin, + lowerLimit = $Locations[0] + options.limit, + upperLimit = $Locations[1] - options.limit; + + // For sliders with multiple handles, + // limit movement to the other handle. + // Apply the margin option by adding it to the handle positions. + if ( $Handles.length > 1 ) { + to = trigger ? Math.max( to, lowerMargin ) : Math.min( to, upperMargin ); + } + + // The limit option has the opposite effect, limiting handles to a + // maximum distance from another. Limit must be > 0, as otherwise + // handles would be unmoveable. 'noLimitOption' is set to 'false' + // for the .val() method, except for pass 4/4. + if ( noLimitOption !== false && options.limit && $Handles.length > 1 ) { + to = trigger ? Math.min ( to, lowerLimit ) : Math.max( to, upperLimit ); + } + + // Handle the step option. + to = $Spectrum.getStep( to ); + + // Limit to 0/100 for .val input, trim anything beyond 7 digits, as + // JavaScript has some issues in its floating point implementation. + to = limit(parseFloat(to.toFixed(7))); + + // Return false if handle can't move. + if ( to === $Locations[trigger] ) { + return false; + } + + // Set the handle to the new position. + handle.css( options.style, to + '%' ); + + // Force proper handle stacking + if ( handle.is(':first-child') ) { + handle.toggleClass(Classes[17], to > 50 ); + } + + // Update locations. + $Locations[trigger] = to; + + // Convert the value to the slider stepping/range. + $Values[trigger] = $Spectrum.fromStepping( to ); + + linkUpdate(triggerPos[trigger]); + + return true; + } + + // Loop values from value method and apply them. + function setValues ( count, values ) { + + var i, trigger, to; + + // With the limit option, we'll need another limiting pass. + if ( options.limit ) { + count += 1; + } + + // 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. + for ( i = 0; i < count; i += 1 ) { + + trigger = i%2; + + // Get the current argument from the array. + to = values[trigger]; + + // Setting with null indicates an 'ignore'. + // Inputting 'false' is invalid. + if ( to !== null && to !== false ) { + + // If a formatted number was passed, attemt to decode it. + if ( typeof to === 'number' ) { + to = String(to); + } + + to = options.format.from( to ); + + // Request an update for all links if the value was invalid. + // Do so too if setting the handle fails. + if ( to === false || isNaN(to) || setHandle( $Handles[trigger], $Spectrum.toStepping( to ), i === (3 - options.dir) ) === false ) { + + linkUpdate(triggerPos[trigger]); + } + } + } + } + + // Set the slider value. + function valueSet ( input ) { + + // LibLink: don't accept new values when currently emitting changes. + if ( $Target[0].LinkIsEmitting ) { + return this; + } + + var count, values = asArray( input ); + + // The RTL settings is implemented by reversing the front-end, + // internal mechanisms are the same. + if ( options.dir && options.handles > 1 ) { + values.reverse(); + } + + // Animation is optional. + // Make sure the initial values where set before using animated + // placement. (no report, unit testing); + if ( options.animate && $Locations[0] !== -1 ) { + addClassFor( $Target, Classes[14], 300 ); + } + + // Determine how often to set the handles. + count = $Handles.length > 1 ? 3 : 1; + + if ( values.length === 1 ) { + count = 1; + } + + setValues ( count, values ); + + // Fire the 'set' event. As of noUiSlider 7, + // this is no longer optional. + fireEvents(['set']); + + return this; + } + + // Get the slider value. + function valueGet ( ) { + + var i, retour = []; + + // Get the value from all handles. + for ( i = 0; i < options.handles; i += 1 ){ + retour[i] = options.format.to( $Values[i] ); + } + + return inSliderOrder( retour ); + } + + // Destroy the slider and unbind all events. + function destroyTarget ( ) { + + // Unbind events on the slider, remove all classes and child elements. + $(this).off(namespace) + .removeClass(Classes.join(' ')) + .empty(); + + delete this.LinkUpdate; + delete this.LinkConfirm; + delete this.LinkDefaultFormatter; + delete this.LinkDefaultFlag; + delete this.reappend; + delete this.vGet; + delete this.vSet; + delete this.getCurrentStep; + delete this.getInfo; + delete this.destroy; + + // Return the original options from the closure. + return originalOptions; + } + + // Get the current step size for the slider. + function getCurrentStep ( ) { + + // Check all locations, map them to their stepping point. + // Get the step point, then find it in the input list. + var retour = $.map($Locations, function( location, index ){ + + var step = $Spectrum.getApplicableStep( location ), + value = $Values[index], + increment = step[2], + decrement = (value - step[2]) >= step[1] ? step[2] : step[0]; + + return [[decrement, increment]]; + }); + + // Return values in the proper order. + return inSliderOrder( retour ); + } + + // Get the original set of options. + function getOriginalOptions ( ) { + return originalOptions; + } + + +// Initialize slider + + // Throw an error if the slider was already initialized. + if ( $Target.hasClass(Classes[0]) ) { + throw new Error('Slider was already initialized.'); + } + + // Create the base element, initialise HTML and set classes. + // Add handles and links. + $Base = addSlider( options.dir, options.ort, $Target ); + $Handles = addHandles( options.handles, options.dir, $Base ); + + // Set the connect classes. + addConnection ( options.connect, $Target, $Handles ); + + // Attach user events. + events( options.events ); + +// Methods + + target.vSet = valueSet; + target.vGet = valueGet; + target.destroy = destroyTarget; + + target.getCurrentStep = getCurrentStep; + target.getOriginalOptions = getOriginalOptions; + + target.getInfo = function(){ + return [ + $Spectrum, + options.style, + options.ort + ]; + }; + + // Use the public value method to set the start values. + $Target.val( options.start ); + +} + + + // Run the standard initializer + function initialize ( originalOptions ) { + + // Throw error if group is empty. + if ( !this.length ){ + throw new Error("noUiSlider: Can't initialize slider on empty selection."); + } + + // Test the options once, not for every slider. + var options = testOptions( originalOptions, this ); + + // Loop all items, and provide a new closed-scope environment. + return this.each(function(){ + closure(this, options, originalOptions); + }); + } + + // Destroy the slider, then re-enter initialization. + function rebuild ( options ) { + + return this.each(function(){ + + // The rebuild flag can be used if the slider wasn't initialized yet. + if ( !this.destroy ) { + $(this).noUiSlider( options ); + return; + } + + // Get the current values from the slider, + // including the initialization options. + var values = $(this).val(), originalOptions = this.destroy(), + + // Extend the previous options with the newly provided ones. + newOptions = $.extend( {}, originalOptions, options ); + + // Run the standard initializer. + $(this).noUiSlider( newOptions ); + + // Place Link elements back. + this.reappend(); + + // If the start option hasn't changed, + // reset the previous values. + if ( originalOptions.start === newOptions.start ) { + $(this).val(values); + } + }); + } + + // Access the internal getting and setting methods based on argument count. + function value ( ) { + return this[0][ !arguments.length ? 'vGet' : 'vSet' ].apply(this[0], arguments); + } + + // Override the .val() method. Test every element. Is it a slider? Go to + // the slider value handling. No? Use the standard method. + // Note how $.fn.val expects 'this' to be an instance of $. For convenience, + // the above 'value' function does too. + $.fn.val = function ( ) { + + // this === instanceof $ + + function valMethod( a ){ + return a.hasClass(Classes[0]) ? value : $val; + } + + var args = arguments, + first = $(this[0]); + + if ( !arguments.length ) { + return valMethod(first).call(first); + } + + // Return the set so it remains chainable + return this.each(function(){ + valMethod($(this)).apply($(this), args); + }); + }; + +// Extend jQuery/Zepto with the noUiSlider method. + $.fn.noUiSlider = function ( options, rebuildFlag ) { + + switch ( options ) { + case 'step': return this[0].getCurrentStep(); + case 'options': return this[0].getOriginalOptions(); + } + + return ( rebuildFlag ? rebuild : initialize ).call(this, options); + }; + +}( window.jQuery || window.Zepto )); diff --git a/distribute/jquery.nouislider.min.css b/distribute/jquery.nouislider.min.css new file mode 100644 index 00000000..fa0bf56b --- /dev/null +++ b/distribute/jquery.nouislider.min.css @@ -0,0 +1,4 @@ +/*! noUiSlider - 7.0.3 - 2014-09-11 16:30:12 */ + + +.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-user-select:none;-ms-touch-action:none;-ms-user-select:none;-moz-user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative}.noUi-base{width:100%;height:100%;position:relative}.noUi-origin{position:absolute;right:0;top:0;left:0;bottom:0}.noUi-handle{position:relative;z-index:1}.noUi-stacking .noUi-handle{z-index:10}.noUi-state-tap .noUi-origin{-webkit-transition:left .3s,top .3s;transition:left .3s,top .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-base{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}.noUi-background{background:#FAFAFA;box-shadow:inset 0 1px 1px #f0f0f0}.noUi-connect{background:#3FB8AF;box-shadow:inset 0 0 3px rgba(51,51,51,.45);-webkit-transition:background 450ms;transition:background 450ms}.noUi-origin{border-radius:2px}.noUi-target{border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-target.noUi-connect{box-shadow:inset 0 0 3px rgba(51,51,51,.45),0 3px 6px -5px #BBB}.noUi-dragable{cursor:w-resize}.noUi-vertical .noUi-dragable{cursor:n-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect,[disabled].noUi-connect{background:#B8B8B8}[disabled] .noUi-handle{cursor:not-allowed} \ No newline at end of file diff --git a/distribute/jquery.nouislider.min.js b/distribute/jquery.nouislider.min.js new file mode 100644 index 00000000..a37563e7 --- /dev/null +++ b/distribute/jquery.nouislider.min.js @@ -0,0 +1,3 @@ +/*! noUiSlider - 7.0.3 - 2014-09-11 16:30:12 */ + +!function(a){"use strict";function b(a,b){return Math.round(a/b)*b}function c(a){return"number"==typeof a&&!isNaN(a)&&isFinite(a)}function d(a){var b=Math.pow(10,7);return Number((Math.round(a*b)/b).toFixed(7))}function e(a,b,c){a.addClass(b),setTimeout(function(){a.removeClass(b)},c)}function f(a){return Math.max(Math.min(a,100),0)}function g(b){return a.isArray(b)?b:[b]}function h(a,b){return 100/(b-a)}function i(a,b){return 100*b/(a[1]-a[0])}function j(a,b){return i(a,a[0]<0?b+Math.abs(a[0]):b-a[0])}function k(a,b){return b*(a[1]-a[0])/100+a[0]}function l(a,b){for(var c=1;a>=b[c];)c+=1;return c}function m(a,b,c){if(c>=a.slice(-1)[0])return 100;var d,e,f,g,i=l(c,a);return d=a[i-1],e=a[i],f=b[i-1],g=b[i],f+j([d,e],c)/h(f,g)}function n(a,b,c){if(c>=100)return a.slice(-1)[0];var d,e,f,g,i=l(c,b);return d=a[i-1],e=a[i],f=b[i-1],g=b[i],k([d,e],(c-f)*h(f,g))}function o(a,c,d,e){if(100===e)return e;var f,g,h=l(e,a);return d?(f=a[h-1],g=a[h],e-f>(g-f)/2?g:f):c[h-1]?a[h-1]+b(e-a[h-1],c[h-1]):e}function p(a,b,d){var e;if("number"==typeof b&&(b=[b]),"[object Array]"!==Object.prototype.toString.call(b))throw new Error("noUiSlider: 'range' contains invalid value.");if(e="min"===a?0:"max"===a?100:parseFloat(a),!c(e)||!c(b[0]))throw new Error("noUiSlider: 'range' value isn't numeric.");d.xPct.push(e),d.xVal.push(b[0]),e?d.xSteps.push(isNaN(b[1])?!1:b[1]):isNaN(b[1])||(d.xSteps[0]=b[1])}function q(a,b,c){return b?void(c.xSteps[a]=i([c.xVal[a],c.xVal[a+1]],b)/h(c.xPct[a],c.xPct[a+1])):!0}function r(a,b,c,d){this.xPct=[],this.xVal=[],this.xSteps=[d||!1],this.xNumSteps=[!1],this.snap=b,this.direction=c;var e,f=this;for(e in a)a.hasOwnProperty(e)&&p(e,a[e],f);f.xNumSteps=f.xSteps.slice(0);for(e in f.xNumSteps)f.xNumSteps.hasOwnProperty(e)&&q(Number(e),f.xNumSteps[e],f)}function s(a,b){if(!c(b))throw new Error("noUiSlider: 'step' is not numeric.");a.singleStep=b}function t(b,c){if("object"!=typeof c||a.isArray(c))throw new Error("noUiSlider: 'range' is not an object.");if(void 0===c.min||void 0===c.max)throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'.");b.spectrum=new r(c,b.snap,b.dir,b.singleStep)}function u(b,c){if(c=g(c),!a.isArray(c)||!c.length||c.length>2)throw new Error("noUiSlider: 'start' option is incorrect.");b.handles=c.length,b.start=c}function v(a,b){if(a.snap=b,"boolean"!=typeof b)throw new Error("noUiSlider: 'snap' option must be a boolean.")}function w(a,b){if(a.animate=b,"boolean"!=typeof b)throw new Error("noUiSlider: 'animate' option must be a boolean.")}function x(a,b){if("lower"===b&&1===a.handles)a.connect=1;else if("upper"===b&&1===a.handles)a.connect=2;else if(b===!0&&2===a.handles)a.connect=3;else{if(b!==!1)throw new Error("noUiSlider: 'connect' option doesn't match handle count.");a.connect=0}}function y(a,b){switch(b){case"horizontal":a.ort=0;break;case"vertical":a.ort=1;break;default:throw new Error("noUiSlider: 'orientation' option is invalid.")}}function z(a,b){if(!c(b))throw new Error("noUiSlider: 'margin' option must be numeric.");if(a.margin=a.spectrum.getMargin(b),!a.margin)throw new Error("noUiSlider: 'margin' option is only supported on linear sliders.")}function A(a,b){if(!c(b))throw new Error("noUiSlider: 'limit' option must be numeric.");if(a.limit=a.spectrum.getMargin(b),!a.limit)throw new Error("noUiSlider: 'limit' option is only supported on linear sliders.")}function B(a,b){switch(b){case"ltr":a.dir=0;break;case"rtl":a.dir=1,a.connect=[0,2,1,3][a.connect];break;default:throw new Error("noUiSlider: 'direction' option was not recognized.")}}function C(a,b){if("string"!=typeof b)throw new Error("noUiSlider: 'behaviour' must be a string containing options.");var c=b.indexOf("tap")>=0,d=b.indexOf("drag")>=0,e=b.indexOf("fixed")>=0,f=b.indexOf("snap")>=0;a.events={tap:c||f,drag:d,fixed:e,snap:f}}function D(a,b){if(a.format=b,"function"==typeof b.to&&"function"==typeof b.from)return!0;throw new Error("noUiSlider: 'format' requires 'to' and 'from' methods.")}function E(b){var c,d={margin:0,limit:0,animate:!0,format:U};return c={step:{r:!1,t:s},start:{r:!0,t:u},connect:{r:!0,t:x},direction:{r:!0,t:B},snap:{r:!1,t:v},animate:{r:!1,t:w},range:{r:!0,t:t},orientation:{r:!1,t:y},margin:{r:!1,t:z},limit:{r:!1,t:A},behaviour:{r:!0,t:C},format:{r:!1,t:D}},b=a.extend({connect:!1,direction:"ltr",behaviour:"tap",orientation:"horizontal"},b),a.each(c,function(a,c){if(void 0===b[a]){if(c.r)throw new Error("noUiSlider: '"+a+"' is required.");return!0}c.t(d,b[a])}),d.style=d.ort?"top":"left",d}function F(a,b,c){var d=a+b[0],e=a+b[1];return c?(0>d&&(e+=Math.abs(d)),e>100&&(d-=e-100),[f(d),f(e)]):[d,e]}function G(a){a.preventDefault();var b,c,d=0===a.type.indexOf("touch"),e=0===a.type.indexOf("mouse"),f=0===a.type.indexOf("pointer"),g=a;return 0===a.type.indexOf("MSPointer")&&(f=!0),a.originalEvent&&(a=a.originalEvent),d&&(b=a.changedTouches[0].pageX,c=a.changedTouches[0].pageY),(e||f)&&(f||void 0!==window.pageXOffset||(window.pageXOffset=document.documentElement.scrollLeft,window.pageYOffset=document.documentElement.scrollTop),b=a.clientX+window.pageXOffset,c=a.clientY+window.pageYOffset),g.points=[b,c],g.cursor=e,g}function H(b,c){var d=a("
").addClass(T[2]),e=["-lower","-upper"];return b&&e.reverse(),d.children().addClass(T[3]+" "+T[3]+e[c]),d}function I(a,b,c){switch(a){case 1:b.addClass(T[7]),c[0].addClass(T[6]);break;case 3:c[1].addClass(T[6]);case 2:c[0].addClass(T[7]);case 0:b.addClass(T[6])}}function J(a,b,c){var d,e=[];for(d=0;a>d;d+=1)e.push(H(b,d).appendTo(c));return e}function K(b,c,d){return d.addClass([T[0],T[8+b],T[4+c]].join(" ")),a("
").appendTo(d).addClass(T[1])}function L(b,c,d){function h(){return B[["width","height"][c.ort]]()}function i(a){var b,c=[D.val()];for(b=0;b1&&(e=1===e?0:1),k(e)}function n(){var a,b;for(a=0;a1),e=u(d[0],c[g],1===d.length),d.length>1&&(e=u(d[1],c[g?0:1],!1)||e),e&&i(["slide"])}function q(b){a("."+T[15]).removeClass(T[15]),b.cursor&&a("body").css("cursor","").off(R),P.off(R),D.removeClass(T[12]),i(["set","change"])}function r(b,c){1===c.handles.length&&c.handles[0].children().addClass(T[15]),b.stopPropagation(),o(S.move,P,p,{start:b.calcPoint,handles:c.handles,positions:[E[0],E[C.length-1]]}),o(S.end,P,q,null),b.cursor&&(a("body").css("cursor",a(b.target).css("cursor")),C.length>1&&D.addClass(T[12]),a("body").on("selectstart"+R,!1))}function s(b){var d,f=b.calcPoint,g=0;b.stopPropagation(),a.each(C,function(){g+=this.offset()[c.style]}),g=g/2>f||1===C.length?0:1,f-=B.offset()[c.style],d=100*f/h(),c.events.snap||e(D,T[14],300),u(C[g],d),i(["slide","set","change"]),c.events.snap&&r(b,{handles:[C[g]]})}function t(a){var b,c;if(!a.fixed)for(b=0;b1&&(b=e?Math.max(b,g):Math.min(b,h)),d!==!1&&c.limit&&C.length>1&&(b=e?Math.min(b,i):Math.max(b,j)),b=H.getStep(b),b=f(parseFloat(b.toFixed(7))),b===E[e]?!1:(a.css(c.style,b+"%"),a.is(":first-child")&&a.toggleClass(T[17],b>50),E[e]=b,L[e]=H.fromStepping(b),l(M[e]),!0)}function v(a,b){var d,e,f;for(c.limit&&(a+=1),d=0;a>d;d+=1)e=d%2,f=b[e],null!==f&&f!==!1&&("number"==typeof f&&(f=String(f)),f=c.format.from(f),(f===!1||isNaN(f)||u(C[e],H.toStepping(f),d===3-c.dir)===!1)&&l(M[e]))}function w(a){if(D[0].LinkIsEmitting)return this;var b,d=g(a);return c.dir&&c.handles>1&&d.reverse(),c.animate&&-1!==E[0]&&e(D,T[14],300),b=C.length>1?3:1,1===d.length&&(b=1),v(b,d),i(["set"]),this}function x(){var a,b=[];for(a=0;a=c[1]?c[2]:c[0];return[[f,e]]});return j(b)}function A(){return d}var B,C,D=a(b),E=[-1,-1],H=c.spectrum,L=[],M=["lower","upper"].slice(0,c.handles);if(c.dir&&M.reverse(),b.LinkUpdate=l,b.LinkConfirm=m,b.LinkDefaultFormatter=c.format,b.LinkDefaultFlag="lower",b.reappend=n,D.hasClass(T[0]))throw new Error("Slider was already initialized.");B=K(c.dir,c.ort,D),C=J(c.handles,c.dir,B),I(c.connect,D,C),t(c.events),b.vSet=w,b.vGet=x,b.destroy=y,b.getCurrentStep=z,b.getOriginalOptions=A,b.getInfo=function(){return[H,c.style,c.ort]},D.val(c.start)}function M(a){if(!this.length)throw new Error("noUiSlider: Can't initialize slider on empty selection.");var b=E(a,this);return this.each(function(){L(this,b,a)})}function N(b){return this.each(function(){if(!this.destroy)return void a(this).noUiSlider(b);var c=a(this).val(),d=this.destroy(),e=a.extend({},d,b);a(this).noUiSlider(e),this.reappend(),d.start===e.start&&a(this).val(c)})}function O(){return this[0][arguments.length?"vSet":"vGet"].apply(this[0],arguments)}var P=a(document),Q=a.fn.val,R=".nui",S=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"},T=["noUi-target","noUi-base","noUi-origin","noUi-handle","noUi-horizontal","noUi-vertical","noUi-background","noUi-connect","noUi-ltr","noUi-rtl","noUi-dragable","","noUi-state-drag","","noUi-state-tap","noUi-active","","noUi-stacking"];r.prototype.getMargin=function(a){return 2===this.xPct.length?i(this.xVal,a):!1},r.prototype.toStepping=function(a){return a=m(this.xVal,this.xPct,a),this.direction&&(a=100-a),a},r.prototype.fromStepping=function(a){return this.direction&&(a=100-a),d(n(this.xVal,this.xPct,a))},r.prototype.getStep=function(a){return this.direction&&(a=100-a),a=o(this.xPct,this.xSteps,this.snap,a),this.direction&&(a=100-a),a},r.prototype.getApplicableStep=function(a){var b=l(a,this.xPct),c=100===a?2:1;return[this.xNumSteps[b-2],this.xVal[b-c],this.xNumSteps[b-c]]},r.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var U={to:function(a){return a.toFixed(2)},from:Number};a.fn.val=function(){function b(a){return a.hasClass(T[0])?O:Q}var c=arguments,d=a(this[0]);return arguments.length?this.each(function(){b(a(this)).apply(a(this),c)}):b(d).call(d)},a.fn.noUiSlider=function(a,b){switch(a){case"step":return this[0].getCurrentStep();case"options":return this[0].getOriginalOptions()}return(b?N:M).call(this,a)}}(window.jQuery||window.Zepto); \ No newline at end of file diff --git a/distribute/jquery.nouislider.pips.min.css b/distribute/jquery.nouislider.pips.min.css new file mode 100644 index 00000000..b7adb727 --- /dev/null +++ b/distribute/jquery.nouislider.pips.min.css @@ -0,0 +1,4 @@ +/*! noUiSlider - 7.0.3 - 2014-09-11 16:30:12 */ + + +.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;font:400 12px Arial;color:#999}.noUi-value{width:40px;position:absolute;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:50px;top:100%;left:0;width:100%}.noUi-value-horizontal{margin-left:-20px;padding-top:20px}.noUi-value-horizontal.noUi-value-sub{padding-top:15px}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{width:15px;margin-left:20px;margin-top:-5px}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px} \ No newline at end of file diff --git a/package.json b/package.json index 0380ff4a..ee7631b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "noUiSlider", - "version": "7.0.2", + "version": "7.0.3", "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-compress": "^0.11.0", diff --git a/src/js/scope_link.js b/src/js/scope_link.js index 38b9cf1c..df9d07fd 100644 --- a/src/js/scope_link.js +++ b/src/js/scope_link.js @@ -36,7 +36,8 @@ // The public API is reversed for rtl sliders, so the changeHandler // should not be aware of the inverted trigger positions. - if ( options.dir ) { + // On rtl slider with one handle, 'lower' should be used. + if ( options.dir && options.handles > 1 ) { trigger = trigger === 1 ? 0 : 1; } diff --git a/tests/link_rtl.js b/tests/link_rtl.js new file mode 100644 index 00000000..e9b0c6ec --- /dev/null +++ b/tests/link_rtl.js @@ -0,0 +1,25 @@ + + test( "Test rtl link integration", function(){ + + Q.html('\ +
\ +
\ + \ +
\ + '); + + var slider = Q.find('#slider'), input = Q.find('#write'); + + slider.noUiSlider({ + range: { min: 0, max: 15 }, + direction: 'rtl', + orientation: 'vertical', + start: 5 + }).Link('lower').to(input); + + equal(input.val(), '5.00'); + + input.val('8').change(); + + equal(input.val(), '8.00'); + }); diff --git a/tests/slider.html b/tests/slider.html index 8926fe9f..b2c4a601 100644 --- a/tests/slider.html +++ b/tests/slider.html @@ -57,6 +57,7 @@ +