diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c954da8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/compiler \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..c95f97b5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "libLink"] + path = libLink + url = https://github.com/leongersen/libLink +[submodule "wnumb"] + path = wnumb + url = https://github.com/leongersen/wnumb diff --git a/Link.js b/Link.js deleted file mode 100644 index 66752ca2..00000000 --- a/Link.js +++ /dev/null @@ -1,389 +0,0 @@ -/**@preserve -$.Link (part of noUiSlider) - WTFPL */ - -/*jslint browser: true */ -/*jslint sub: true */ -/*jslint white: true */ - -(function( $ ){ - - 'use strict'; - - // 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("(Link) '"+a+"' can't match '"+b+"'.'"); - } - } - - // Test in an object is an instance of jQuery or Zepto. - function isInstance ( a ) { - return a instanceof $ || ( $['zepto'] && $['zepto']['isZ'](a) ); - } - -var -/** @const */ Formatting = [ -/* 0 */ 'decimals' -/* 1 */ ,'mark' -/* 2 */ ,'thousand' -/* 3 */ ,'prefix' -/* 4 */ ,'postfix' -/* 5 */ ,'encoder' -/* 6 */ ,'decoder' -/* 7 */ ,'negative' -/* 8 */ ,'negativeBefore' -/* 9 */ ,'to' -/* 10 */ ,'from' - ], -/** @const */ FormatDefaults = [ -/* 0 */ 2 -/* 1 */ ,'.' -/* 2 */ ,'' -/* 3 */ ,'' -/* 4 */ ,'' -/* 5 */ ,function(a){ return a; } -/* 6 */ ,function(a){ return a; } -/* 7 */ ,'-' -/* 8 */ ,'' -/* 9 */ ,function(a){ return a; } -/* 10 */ ,function(a){ return a; } - ]; - - - /** @constructor */ - function Format( options ){ - - // If no settings where provided, the defaults will be loaded. - if ( options === undefined ){ - options = {}; - } - - if ( typeof options !== 'object' ){ - throw new Error("(Format) 'format' option must be an object."); - } - - var settings = {}; - - // Copy all values into a new object. - $(Formatting).each(function(i, val){ - - if ( options[val] === undefined ){ - - settings[val] = FormatDefaults[i]; - - // When we aren't loading defaults, validate the entry. - } else if ( (typeof options[val]) === (typeof FormatDefaults[i]) ) { - - // Support for up to 7 decimals. - // More can't be guaranteed due to floating point issues. - if ( val === 'decimals' ){ - if ( options[val] < 0 || options[val] > 7 ){ - throw new Error("(Format) 'format.decimals' option must be between 0 and 7."); - } - } - - settings[val] = options[val]; - - // If the value isn't valid, emit an error. - } else { - throw new Error("(Format) 'format."+val+"' must be a " + typeof FormatDefaults[i] + "."); - } - }); - - // Some values can't be extracted from a - // string if certain combinations are present. - throwEqualError(settings, 'mark', 'thousand'); - throwEqualError(settings, 'prefix', 'negative'); - throwEqualError(settings, 'prefix', 'negativeBefore'); - - this.settings = settings; - } - - // Shorthand for internal value get - Format.prototype.v = function ( a ) { - return this.settings[a]; - }; - - Format.prototype.to = function ( number ) { - - function reverse ( a ) { - return a.split('').reverse().join(''); - } - - number = this.v('encoder')( number ); - - var decimals = this.v('decimals'), - negative = '', preNegative = '', base = '', mark = ''; - - // Rounding away decimals might cause a value of -0 - // when using very small ranges. Remove those cases. - if ( parseFloat(number.toFixed(decimals)) === 0 ) { - number = '0'; - } - - if ( number < 0 ) { - negative = this.v('negative'); - preNegative = this.v('negativeBefore'); - } - - // Round to proper decimal count - number = Math.abs(number).toFixed(decimals).toString(); - number = number.split('.'); - - // Group numbers in sets of three. - if ( this.v('thousand') ) { - base = reverse(number[0]).match(/.{1,3}/g); - base = reverse(base.join(reverse( this.v('thousand') ))); - } else { - base = number[0]; - } - - // Ignore the decimal separator if decimals are set to 0. - if ( this.v('mark') && number.length > 1 ) { - mark = this.v('mark') + number[1]; - } - - // Return the finalized formatted number. - return this.v('to')(preNegative + - this.v('prefix') + - negative + - base + - mark + - this.v('postfix')); - }; - - Format.prototype.from = function ( input ) { - - function esc(s){ - return s.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&'); - } - - var isNeg; - // The set request might want to ignore this handle. - // Test for 'undefined' too, as a two-handle slider - // can still be set with an integer. - if ( input === null || input === undefined ) { - return false; - } - - input = this.v('from')(input); - - // Remove formatting and set period for float parsing. - input = input.toString(); - - // Replace the preNegative indicator. - isNeg = input.replace(new RegExp('^' + esc( this.v('negativeBefore') )), ''); - - // Check if the value changed by removing the negativeBefore symbol. - if( input !== isNeg ) { - input = isNeg; - isNeg = '-'; - } else { - isNeg = ''; - } - - // If prefix is set and the number is actually prefixed. - input = input.replace(new RegExp('^'+esc( this.v('prefix') )), ''); - - // Only replace if a negative sign is set. - if ( this.v('negative') ) { - - // Reset isNeg to prevent double '-' insertion. - isNeg = ''; - - // Reset the negative sign to '-' - input = input.replace(new RegExp('^'+esc( this.v('negative') )), '-'); - } - - // Clean the input string - input = input - // If postfix is set and the number is postfixed. - .replace( new RegExp(esc( this.v('postfix') ) + '$'), '') - // Remove the separator every three digits. - .replace( new RegExp(esc( this.v('thousand') ), 'g'), '') - // Set the decimal separator back to period. - .replace( this.v('mark'), '.'); - - // Run the user defined decoder. Returns input by default. - input = this.v('decoder')( parseFloat( isNeg + input ) ); - - // Ignore invalid input - if (isNaN( input )) { - return false; - } - - return input; - }; - - -/** @expose */ -/** @constructor */ - function Link ( entry, update ) { - - if ( typeof entry !== "object" ) { - $.error("(Link) Initialize with an object."); - } - - // Make sure Link isn't called as a function, in which case - // the 'this' scope would be the window. - return new Link.prototype.init( entry['target']||function(){}, entry['method'], entry['format']||{}, update ); - } - - Link.prototype.setTooltip = function ( target, method ) { - - // By default, use the 'html' method. - this.method = method || 'html'; - - // Use jQuery to create the element - this.el = $( target.replace('-tooltip-', '') || '
' )[0]; - }; - - Link.prototype.setHidden = function ( target ) { - - this.method = 'val'; - - this.el = document.createElement('input'); - this.el.name = target; - this.el.type = 'hidden'; - }; - - Link.prototype.setField = function ( target ) { - - // Returns nulled array. - function at(a,b,c){ - return [c?a:b, c?b:a]; - } - - // In IE < 9, .bind() isn't available, need this link in .change(). - var that = this; - - // Default to .val if this is an input element. - this.method = 'val'; - // Set the slider to a new value on change. - this.target = target.on('change', function( e ){ - that.obj.val( - at(null, $(e.target).val(), that.N), - { 'link': that, 'set': true } - ); - }); - }; - - - // Initialisor - /** @constructor */ - Link.prototype.init = function ( target, method, format, update ) { - - // Write all formatting to this object. - // No validation needed, as we'll merge these with the parent - // format options first. - this.formatting = format; - - // Store the update option. - this.update = !update; - - // If target is a string, a new hidden input will be created. - if ( typeof target === 'string' && target.indexOf('-tooltip-') === 0 ) { - this.setTooltip( target, method ); - return; - } - - // If the string doesn't begin with '-', which is reserved, add a new hidden input. - if ( typeof target === 'string' && target.indexOf('-') !== 0 ) { - this.setHidden( target ); - return; - } - - // The target can also be a function, which will be called. - if ( typeof target === 'function' ) { - this.target = false; - this.method = target; - return; - } - - if ( isInstance(target) ) { - // If a jQuery/Zepto input element is provided, but no method is set, - // the element can assume it needs to respond to 'change'... - - if ( !method ) { - - if ( target.is('input, select, textarea') ) { - this.setField( target ); - return; - } - - // If no method is set, and we are not auto-binding an input, default to 'html'. - method = 'html'; - } - - // The method must exist on the element. - if ( typeof method === 'function' || (typeof method === 'string' && target[method]) ) { - this.method = method; - this.target = target; - return; - } - } - - // Nothing matched, throw error. - throw new RangeError("(Link) Invalid Link."); - }; - - // Provides external items with the slider value. - Link.prototype.write = function ( value, handle, slider, update ) { - - // Don't synchronize this Link. - if ( this.update && update === false ) { - return; - } - - this.actual = value; - - // Format values for display. - value = this.format( value ); - - // Store the numerical value. - this.saved = value; - - // Branch between serialization to a function or an object. - if ( typeof this.method === 'function' ) { - // When target is undefined, the target was a function. - // In that case, provided the slider as the calling scope. - // Use [0] to get the DOM element, not the $ instance. - this.method.call( this.target[0] || slider[0], value, handle, slider ); - } else { - this.target[ this.method ]( value, handle, slider ); - } - }; - - // Set formatting options. - Link.prototype.setFormatting = function ( options ) { - this.formatting = new Format($.extend({}, - options, - this.formatting instanceof Format ? this.formatting.settings : this.formatting - )); - }; - - Link.prototype.setObject = function ( obj ) { - this.obj = obj; - }; - - Link.prototype.setIndex = function ( index ) { - this.N = index; - }; - - // Parses slider value to user defined display. - Link.prototype.format = function ( a ) { - return this.formatting.to(a); - }; - - // Converts a formatted value back to a real number. - Link.prototype.getValue = function ( a ) { - return this.formatting.from(a); - }; - - // We can now test for Link.init to be an instance of Link. - Link.prototype.init.prototype = Link.prototype; - - /** @expose */ - $.Link = Link; - -}( window['jQuery'] || window['Zepto'] )); diff --git a/README.md b/README.md index 82453318..c2777591 100644 --- a/README.md +++ b/README.md @@ -7,30 +7,9 @@ Documentation An extensive documentation, including **examples**, **options** and **configuration details**, is available here: [noUiSlider documentation](http://refreshless.com/nouislider/). -Changes -------- - -**Changelog for version 6.2.0:** -*(Compatible with 6.0.0)* - -+ Removed the previously added `.classVal` and replaced it with a lightweight solution. -+ Fixed a bug in non-linear stepping for RTL sliders. (#262) -+ Added checks for `min` and `max` in `range`. (#255) -+ Added the minified version in the source, so it can be managed with Bower. (#252) - -**Changelog for version 6.0.0:** - -**Please note:** noUiSlider 6 is a *major* revision, which means it isn't compatible with version 5. Your stylesheet will keep working, but the JavaScript API has changed, and your current implementation will no longer work. - -+ Added optional **non-linear** ranges, including stepping. -+ Added new behaviour settings. -+ Added object-oriented serialization. -+ Change events to use jQuery's/Zepto's `.on` and `.off`. -+ Removed `block` event. - Unit Testing ------------ -Unit tests where added with noUiSlider 6. Coverage of `$.Link` and value handling is near complete, but due to the sensitivity of events across browsers, event testing is a little lacking. +Unit tests where overhauled for noUiSlider 7. Most code is now covered, with events testing being slightly lacking due to it's browser dependant nature. Version numbering ------------------------------ @@ -41,34 +20,4 @@ Compression and Error checking ------------------------------ The plugin code is checked using ([JsLint](http://jslint.com/)). Any remaining errors and warnings are intentional. -The plugin is compressed using the ([Google Closure Compiler](http://closure-compiler.appspot.com/home)). The source was adapted to facilitate the `ADVANCED_OPTIMIZATIONS` level. `$.Link` is merged into the file. On Windows, the following BAT script can be used to run the compiler. On OS X or Linux enviroments, simply run the `java -jar` command from the command line. - -```bat -@echo off - -:: Set all paths as variables -set "jquery=.\externs\jquery-1.8.js" -set "link=..\source\Link.js" -set "source=..\source\jquery.nouislider.js" -set "result=..\source\jquery.nouislider.min.js" - -:: Remove the existing file so we can be sure a new one is generated. -if exist %result% ( - del %result% -) - -echo "Removed %result%, ready." - -PAUSE - -java -jar .\compiler\compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --externs %jquery% --warning_level VERBOSE --js %link% --js %source% --js_output_file %result% - -echo "Done." -PAUSE -``` - -Known issues ------------- -There are some minor issues remaining in noUiSlider. It is a priority to fix these issues, but they may be fixed by browser updates in the future. - -+ Firefox and Safari on Windows will emulate mouse-events on touch screens, but prefer scrolling to dragging the slider. +The plugin is compressed using the ([Google Closure Compiler](http://closure-compiler.appspot.com/home)). The source was initialy adapted to facilitate the `ADVANCED_OPTIMIZATIONS` level, but the risks this poses regarding to unintented renaming proved problematic. On Windows, the BAT script in this repository can be used to run the compiler. On OS X or Linux enviroments, simply run the `java -jar` command from the command line. diff --git a/compile.bat b/compile.bat new file mode 100644 index 00000000..a033f66c --- /dev/null +++ b/compile.bat @@ -0,0 +1,42 @@ +@echo off + +:: Set all paths as variables +set "wnumb=wnumb\wNumb.js" +set "wnumbMin=wnumb\wNumb.min.js" +set "link=libLink\jquery.liblink.js" + +set "jquery=compiler\jquery-1.9.js" +set "zepto=compiler\zepto.js" + +set "range=src\module.range.js" +set "options=src\module.options.js" +set "source=src\module.base.js" +set "pips=src\optional.pips.js" + +set "nouislidermin=jquery.nouislider.min.js" +set "nouisliderfull=jquery.nouislider.full.min.js" + +set "compiler=compiler\compiler.jar" +set "level=--compilation_level SIMPLE_OPTIMIZATIONS" +set "verbose=--warning_level VERBOSE" + +:: Remove the existing file so we can be sure a new one is generated. +if exist %nouislidermin% ( + del %nouislidermin% + echo "Removed %nouislidermin%." +) +if exist %nouisliderfull% ( + del %nouisliderfull% + echo "Removed %nouisliderfull%," +) + +echo "Ready." + +PAUSE + +java -jar %compiler% %level% %verbose% --js %wnumb% --js_output_file %wnumbMin% +java -jar %compiler% %level% %verbose% --externs %jquery% --externs %zepto% --js %range% --js %options% --js %source% --js_output_file %nouislidermin% +java -jar %compiler% %level% %verbose% --externs %jquery% --externs %zepto% --js %wnumb% --js %link% --js %range% --js %options% --js %source% --js %pips% --js_output_file %nouisliderfull% + +echo "Done." +PAUSE diff --git a/jquery.nouislider.css b/jquery.nouislider.css index 8bcf94cf..1e34ac94 100644 --- a/jquery.nouislider.css +++ b/jquery.nouislider.css @@ -13,6 +13,9 @@ -moz-box-sizing: border-box; box-sizing: border-box; } +.noUi-target { + position: relative; +} .noUi-base { width: 100%; height: 100%; @@ -47,6 +50,15 @@ cursor: inherit !important; } +/* Painting and performance; + * Browsers can paint handles in their own layer. + */ +.noUi-origin, +.noUi-handle { + -webkit-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); +} + /* Slider size and handle placement; */ .noUi-horizontal { @@ -58,12 +70,6 @@ left: -17px; top: -6px; } -.noUi-horizontal.noUi-extended { - padding: 0 15px; -} -.noUi-horizontal.noUi-extended .noUi-origin { - right: -15px; -} .noUi-vertical { width: 18px; } @@ -73,12 +79,6 @@ left: -6px; top: -17px; } -.noUi-vertical.noUi-extended { - padding: 15px 0; -} -.noUi-vertical.noUi-extended .noUi-origin { - bottom: -15px; -} /* Styling; */ diff --git a/jquery.nouislider.full.min.js b/jquery.nouislider.full.min.js new file mode 100644 index 00000000..215ac05a --- /dev/null +++ b/jquery.nouislider.full.min.js @@ -0,0 +1,33 @@ +(function(){function c(a){return a.split("").reverse().join("")}function l(a,b){return a.substring(0,b.length)===b}function q(a,b,d){if((a[b]||a[d])&&a[b]===a[d])throw Error(b);}function m(a,b,d,e,n,h,w,k,A,H,D,g){w=g;var l,s=D="";h&&(g=h(g));if("number"!==typeof g||!isFinite(g))return!1;a&&0===parseFloat(g.toFixed(a))&&(g=0);0>g&&(l=!0,g=Math.abs(g));!1!==a&&(h=g,g=Math.pow(10,a),g=(Math.round(h*g)/g).toFixed(a));g=g.toString();-1!==g.indexOf(".")?(a=g.split("."),h=a[0],d&&(D=d+a[1])):h=g;b&&(h= +c(h).match(/.{1,3}/g),h=c(h.join(c(b))));l&&k&&(s+=k);e&&(s+=e);l&&A&&(s+=A);s=s+h+D;n&&(s+=n);H&&(s=H(s,w));return s}function u(a,b,d,c,e,h,w,k,A,H,D,g){var m;a="";D&&(g=D(g));if(!g||"string"!==typeof g)return!1;k&&l(g,k)&&(g=g.replace(k,""),m=!0);c&&l(g,c)&&(g=g.replace(c,""));A&&l(g,A)&&(g=g.replace(A,""),m=!0);if(c=e)c=g.slice(-1*e.length)===e;c&&(g=g.slice(0,-1*e.length));b&&(g=g.split(b).join(""));d&&(g=g.replace(d,"."));m&&(a+="-");a=(a+g).replace(/[^0-9\.\-.]/g,"");if(""===a)return!1;a=Number(a); +w&&(a=w(a));return"number"===typeof a&&isFinite(a)?a:!1}function a(a){var b,d,c,n={};for(b=0;bc)n[d]=c;else throw Error(d);else if("encoder"===d||"decoder"===d||"edit"===d||"undo"===d)if("function"===typeof c)n[d]=c;else throw Error(d);else if("string"===typeof c)n[d]=c;else throw Error(d);q(n,"mark","thousand");q(n,"prefix","negative");q(n,"prefix", +"negativeBefore");return n}function b(a,b,d){var c,n=[];for(c=0;c"),!0},function(a){if("string"===typeof a&&0!==a.indexOf("-")){this.method="val";var b=document.createElement("input");b.name=a;b.type="hidden";this.target=this.el=c(b);return!0}},function(a){if("function"===typeof a)return this.target=!1,this.method=a,!0},function(a,b){if(l(a)&&!b)return a.is("input, select, textarea")?(this.method="val",this.target=a.on("change.liblink",this.changeHandler)): +(this.target=a,this.method="html"),!0},function(a,b){if(l(a)&&("function"===typeof b||"string"===typeof b&&a[b]))return this.method=b,this.target=a,!0}];q.prototype.set=function(a){var b=Array.prototype.slice.call(arguments).slice(1);this.lastSetValue=this.formatInstance.to(a);b.unshift(this.lastSetValue);("function"===typeof this.method?this.method:this.target[this.method]).apply(this.target,b)};m.prototype.push=function(a,b){this.items.push(a);b&&this.elements.push(b)};m.prototype.reconfirm=function(a){var b; +for(b=0;b=b[d];)d+=1;return d}function u(a,b,d,c){this.xPct=[];this.xVal=[];this.xSteps=[c||!1];this.xNumSteps=[!1];this.snap=b;this.direction=d;for(var f in a)if(a.hasOwnProperty(f)){b=f;d=a[f];c=void 0;"number"===typeof d&&(d=[d]);if("[object Array]"!==Object.prototype.toString.call(d))throw Error("noUiSlider: 'range' contains invalid value.");c="min"===b?0: +"max"===b?100:parseFloat(b);if(!l(c)||!l(d[0]))throw Error("noUiSlider: 'range' value isn't numeric.");this.xPct.push(c);this.xVal.push(d[0]);c?this.xSteps.push(isNaN(d[1])?!1:d[1]):isNaN(d[1])||(this.xSteps[0]=d[1])}this.xNumSteps=this.xSteps.slice(0);for(f in this.xNumSteps)this.xNumSteps.hasOwnProperty(f)&&(a=Number(f),(b=this.xNumSteps[f])&&(this.xSteps[a]=q([this.xVal[a],this.xVal[a+1]],b)/(100/(this.xPct[a+1]-this.xPct[a]))))}u.prototype.getMargin=function(a){return 2===this.xPct.length?q(this.xVal, +a):!1};u.prototype.toStepping=function(a){var b=this.xVal,c=this.xPct;if(a>=b.slice(-1)[0])a=100;else{var e=m(a,b),f,l;f=b[e-1];l=b[e];b=c[e-1];c=c[e];f=[f,l];a=q(f,0>f[0]?a+Math.abs(f[0]):a-f[0]);a=b+a/(100/(c-b))}this.direction&&(a=100-a);return a};u.prototype.fromStepping=function(a){this.direction&&(a=100-a);var b;var c=this.xVal;b=this.xPct;if(100<=a)b=c.slice(-1)[0];else{var e=m(a,b),f,l;f=c[e-1];l=c[e];c=b[e-1];f=[f,l];b=100/(b[e]-c)*(a-c)*(f[1]-f[0])/100+f[0]}a=Math.pow(10,7);return Number((Math.round(b* +a)/a).toFixed(7))};u.prototype.getStep=function(a){this.direction&&(a=100-a);var b=this.xPct,c=this.xSteps,e=this.snap;if(100!==a){var f=m(a,b);e?(c=b[f-1],b=b[f],a=a-c>(b-c)/2?b:c):(c[f-1]?(e=b[f-1],c=c[f-1],b=Math.round((a-b[f-1])/c)*c,b=e+b):b=a,a=b)}this.direction&&(a=100-a);return a};u.prototype.getApplicableStep=function(a){var b=m(a,this.xPct);a=100===a?2:1;return[this.xNumSteps[b-2],this.xVal[b-a],this.xNumSteps[b-a]]};u.prototype.convert=function(a){return this.getStep(this.toStepping(a))}; +c.noUiSlider={Spectrum:u}})(window.jQuery||window.Zepto);(function(c){function l(a){return"number"===typeof a&&!isNaN(a)&&isFinite(a)}function q(a,b){if(!l(b))throw Error("noUiSlider: 'step' is not numeric.");a.singleStep=b}function m(a,b){if("object"!==typeof b||c.isArray(b))throw Error("noUiSlider: 'range' is not an object.");if(void 0===b.min||void 0===b.max)throw Error("noUiSlider: Missing 'min' or 'max' in 'range'.");a.spectrum=new c.noUiSlider.Spectrum(b,a.snap,a.dir,a.singleStep)}function u(a,b){var d=b;b=c.isArray(d)?d:[d];if(!c.isArray(b)||!b.length|| +2
").addClass(h[2]),e=["-lower","-upper"];a&&e.reverse();d.children().addClass(h[3]+" "+h[3]+e[b]);return d}function u(a,b,c){switch(a){case 1:b.addClass(h[7]);c[0].addClass(h[6]);break;case 3:c[1].addClass(h[6]);case 2:c[0].addClass(h[7]);case 0:b.addClass(h[6])}}function a(a,b,c){var d,e=[];for(d=0;d").appendTo(d).addClass(h[1])}function d(d,k,e){function f(){return B[["width","height"][k.ort]]()}function m(a){var b,c=[v.val()];for(b=0;bd&&(e+=Math.abs(d)),100=c[1]?c[2]:c[0],c[2]]]});return g(a)};d.getInfo=function(){return[E,k.style,k.ort]};v.val(k.start)}function e(a){if(!this.length)throw Error("noUiSlider: Can't initialize slider on empty selection.");var b=c.noUiSlider.testOptions(a,this);return this.each(function(){d(this, +b,a)})}function f(a){return this.each(function(){if(this.destroy){var b=c(this).val(),d=this.destroy(),e=c.extend({},d,a);c(this).noUiSlider(e);this.reappend();d.start===e.start&&c(this).val(b)}else c(this).noUiSlider(a)})}function z(){return this[0][arguments.length?"vSet":"vGet"].apply(this[0],arguments)}var p=c(document),r=c.fn.val,n=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"},h="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".split(" ");c.fn.val=function(){var a=arguments,b=c(this[0]);return arguments.length?this.each(function(){(c(this).hasClass(h[0])?z:r).apply(c(this),a)}):(b.hasClass(h[0])?z:r).call(b)};c.fn.noUiSlider= +function(a,b){return(b?f:e).call(this,a)}})(window.jQuery||window.Zepto);(function(c){function l(a){return c.grep(a,function(b,d){return d===c.inArray(b,a)})}function q(a,b,d,e){if("range"===b||"steps"===b)return a.xVal;if("count"===b){b=100/(d-1);var f,l=0;for(d=[];100>=(f=l++*b);)d.push(f);b="positions"}if("positions"===b)return c.map(d,function(b){return a.fromStepping(e?a.getStep(b):b)});if("values"===b)return e?c.map(d,function(b){return a.fromStepping(a.getStep(a.toStepping(b)))}):d}function m(a,b,d,e){var f=a.direction,m={},p=a.xVal[0],r=a.xVal[a.xVal.length-1], +n=!1,h=!1,w=0;a.direction=0;e=l(e.slice().sort(function(a,b){return a-b}));e[0]!==p&&(e.unshift(p),n=!0);e[e.length-1]!==r&&(e.push(r),h=!0);c.each(e,function(f){var l,p,r,g=e[f],q=e[f+1],s,u,x,G;"steps"===d&&(l=a.xNumSteps[f]);l||(l=q-g);if(!1!==g&&void 0!==q)for(p=g;p<=q;p+=l){s=a.toStepping(p);r=s-w;x=r/b;x=Math.round(x);G=r/x;for(r=1;r<=x;r+=1)u=w+r*G,m[u.toFixed(5)]=["x",0];x=-1");m.addClass("noUi-pips noUi-pips-"+p);c.each(e,function(a,b){d&&(a=100-a);m.append("
");b[1]&&m.append("
"+Math.round(b[0])+"
")});return m}c.fn.noUiSlider_pips=function(a){var b=a.mode,d=a.density||1,e=a.filter||!1,f=a.values||!1, +l=a.stepped||!1;return this.each(function(){var a=this.getInfo(),r=q(a[0],b,f,l),r=m(a[0],d,b,r);return c(this).append(u(a[1],a[2],a[0].direction,r,e))})}})(window.jQuery||window.Zepto); diff --git a/jquery.nouislider.js b/jquery.nouislider.js deleted file mode 100644 index 1b55831d..00000000 --- a/jquery.nouislider.js +++ /dev/null @@ -1,1291 +0,0 @@ -/**@preserve -$.fn.noUiSlider - WTFPL - refreshless.com/nouislider/ */ - -/*jslint browser: true */ -/*jslint sub: true */ -/*jslint white: true */ -/*jslint continue: true */ -/*jslint plusplus: true */ - -(function( $ ){ - - 'use strict'; - - 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 */ ,'noUi-extended' -/* 17 */ ,'noUi-stacking' - ]; - - -// General helpers - - // Limits a value to 0 - 100 - function limit ( a ) { - return Math.max(Math.min(a, 100), 0); - } - - // Round a value to the closest 'to'. - function closest ( value, to ) { - return Math.round(value / to) * to; - } - - // Determine the size of a sub-range in relation to a full range. - function subRangeRatio ( pa, pb ) { - return (100 / (pb - pa)); - } - - -// Type validation - - // Checks whether a value is numerical. - function isNumeric ( a ) { - return typeof a === 'number' && !isNaN( a ) && isFinite( a ); - } - - // Wraps a variable as an array, if it isn't one yet. - function asArray ( a ) { - return $.isArray(a) ? a : [a]; - } - - -// Class handling - - // Sets a class and removes it after [duration] ms. - function addClassFor ( element, className, duration ) { - element.addClass(className); - setTimeout(function(){ - element.removeClass(className); - }, duration); - } - - -// Value calculation - - // (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]; - } - - // (percentage) - function toStepping ( options, value ) { - - if ( value >= options.xVal.slice(-1)[0] ){ - return 100; - } - - var j = 1, va, vb, pa, pb; - while ( value >= options.xVal[j] ){ - j++; - } - - va = options.xVal[j-1]; - vb = options.xVal[j]; - pa = options.xPct[j-1]; - pb = options.xPct[j]; - - return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb)); - } - - // (value) - function fromStepping ( options, value ) { - - // There is no range group that fits 100 - if ( value >= 100 ){ - return options.xVal.slice(-1)[0]; - } - - var j = 1, va, vb, pa, pb; - while ( value >= options.xPct[j] ){ - j++; - } - - va = options.xVal[j-1]; - vb = options.xVal[j]; - pa = options.xPct[j-1]; - pb = options.xPct[j]; - - return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb)); - } - - // (percentage) Get the step that applies at a certain value. - function getStep ( options, value ){ - - var j = 1, a, b; - - // Find the proper step for rtl sliders by search in inverse direction. - // Fixes issue #262. - while ( (options.dir ? (100 - value) : value) >= options.xPct[j] ){ - j++; - } - - if ( options.snap ) { - - a = options.xPct[j-1]; - b = options.xPct[j]; - - if ((value - a) > ((b-a)/2)){ - return b; - } - - return a; - } - - if ( !options.xSteps[j-1] ){ - return value; - } - - return options.xPct[j-1] + closest( - value - options.xPct[j-1], - options.xSteps[j-1] - ); - } - - -// 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; - } - - -// Input validation - - 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.xSteps[0] = 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'."); - } - - // Loop all entries. - $.each( entry, function ( index, value ) { - - var percentage; - - // Wrap numerical input in an array. - if ( typeof value === "number" ) { - value = [value]; - } - - // Reject any invalid input. - if ( !$.isArray( value ) ){ - 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. - parsed.xPct.push( percentage ); - parsed.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] ) ) { - parsed.xSteps[0] = value[1]; - } - } else { - parsed.xSteps.push( isNaN(value[1]) ? false : value[1] ); - } - }); - - $.each(parsed.xSteps, function(i,n){ - - // Ignore 'false' stepping. - if ( !n ) { - return true; - } - - // Check if step fits. Not required, but this might serve some goal. - // !((parsed.xVal[i+1] - parsed.xVal[i]) % n); - - // Factor to range ratio - parsed.xSteps[i] = fromPercentage([ - parsed.xVal[i] - ,parsed.xVal[i+1] - ], n) / subRangeRatio ( - parsed.xPct[i], - parsed.xPct[i+1] ); - }); - } - - function testStart ( parsed, entry ) { - - if ( typeof entry === "number" ) { - entry = [entry]; - } - - // Validate input. Values aren't tested, the internal Link will do - // that and 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 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 ( parsed.xPct.length > 2 ) { - throw new Error("noUiSlider: 'margin' option is only supported on linear sliders."); - } - - // Parse value to range and store. As xVal is checked - // to be no bigger than 2, use it as range. - parsed.margin = fromPercentage(parsed.xVal, entry); - - if ( !isNumeric(entry) ){ - throw new Error("noUiSlider: 'margin' option must be numeric."); - } - } - - 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, - extend = entry.indexOf('extend') >= 0, - drag = entry.indexOf('drag') >= 0, - fixed = entry.indexOf('fixed') >= 0, - snap = entry.indexOf('snap') >= 0; - - parsed.events = { - tap: tap || snap, - extend: extend, - drag: drag, - fixed: fixed, - snap: snap - }; - } - - function testSerialization ( parsed, entry, sliders ) { - - parsed.ser = [ entry['lower'], entry['upper'] ]; - parsed.formatting = entry['format']; - - $.each( parsed.ser, function( index, linkInstances ){ - - // Check if the provided option is an array. - if ( !$.isArray(linkInstances) ) { - throw new Error("noUiSlider: 'serialization."+(!index ? 'lower' : 'upper')+"' must be an array."); - } - - $.each(linkInstances, function(){ - - // Check if entry is a Link. - if ( !(this instanceof $.Link) ) { - throw new Error("noUiSlider: 'serialization."+(!index ? 'lower' : 'upper')+"' can only contain Link instances."); - } - - // Assign properties. - this.setIndex ( index ); - this.setObject( sliders ); - this.setFormatting( entry['format'] ); - }); - }); - - // If the slider has two handles and is RTL, - // reverse the serialization input. For one handle, - // lower is still lower. - if ( parsed.dir && parsed.handles > 1 ) { - parsed.ser.reverse(); - } - } - - // Test all developer settings and parse to assumption-safe values. - function test ( options, sliders ){ - - /* Every input option is tested and parsed. This'll prevent - endless validation in internal methods. These tests are - structured with an item for every option available. An - option can be marked as required by setting the 'r' flag. - The testing function is provided with three arguments: - - The provided value for the option; - - A reference to the options object; - - The name for the option; - - The testing function returns false when an error is detected, - or true when everything is OK. It can also modify the option - object, to make sure all values can be correctly looped elsewhere. */ - - var parsed = { - xPct: [] - ,xVal: [] - ,xSteps: [ false ] - ,margin: 0 - }, 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 }, - 'range': { r: true, t: testRange }, - 'snap': { r: false, t: testSnap }, - 'orientation': { r: false, t: testOrientation }, - 'margin': { r: false, t: testMargin }, - 'behaviour': { r: true, t: testBehaviour }, - 'serialization': { r: true, t: testSerialization } - }; - - // Set defaults where applicable. - options = $.extend({ - 'connect': false, - 'direction': 'ltr', - 'behaviour': 'tap', - 'orientation': 'horizontal' - }, options); - - // Make sure the test for serialization runs. - options['serialization'] = $.extend({ - 'lower': [] - ,'upper': [] - ,'format': {} - }, options['serialization']); - - // 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 ( options[name] === undefined ) { - - if ( test.r ) { - throw new Error("noUiSlider: '" + name + "' is required."); - } - - return true; - } - - test.t( parsed, options[name], sliders ); - }); - - // Pre-define the styles. - parsed.style = parsed.ort ? 'top' : 'left'; - - return parsed; - } - - -// DOM additions - - // Append a handle to the base. - function addHandle ( options, index ) { - - var handle = $('
').addClass( Classes[2] ), - additions = [ '-lower', '-upper' ]; - - if ( options.dir ) { - additions.reverse(); - } - - handle.children().addClass( - Classes[3] + " " + Classes[3]+additions[index] - ); - - return handle; - } - - // Create a copy of an element-creating Link. - function addElement ( handle, link ) { - - // If the Link requires creation of a new element, - // create this element and return a new Link instance. - if ( link.el ) { - - link = new $.Link({ - 'target': $(link.el).clone().appendTo( handle ), - 'method': link.method, - 'format': link.formatting - }, true); - } - - // Otherwise, return the reference. - return link; - } - - // Loop all links for a handle. - function addElements ( elements, handle, formatting ) { - - var index, list = [], standard = new $.Link({}, true); - - // Use the Link interface to provide unified - // formatting for the .val() method. - standard.setFormatting(formatting); - - // The list now contains at least one element. - list.push( standard ); - - // Loop all links in either 'lower' or 'upper'. - for ( index = 0; index < elements.length; index++ ) { - list.push(addElement(handle, elements[index])); - } - - return list; - } - - // Go over all Links and assign them to a handle. - function addLinks ( options, handles ) { - - var index, links = []; - - // Copy the links into a new array, instead of modifying - // the 'options.ser' list. This allows replacement of the invalid - // '.el' Links, while the others are still passed by reference. - for ( index = 0; index < options.handles; index++ ) { - - // Append a new array. - links[index] = addElements( - options.ser[index], - handles[index].children(), - options.formatting - ); - } - - return links; - } - - // 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 and loop Link elements. - function addHandles ( options, base ) { - - var index, handles = []; - - // Append handles. - for ( index = 0; index < options.handles; index++ ) { - - // Keep a list of all added handles. - handles.push( addHandle( options, index ).appendTo(base) ); - } - - return handles; - } - - // Initialize a single slider. - function addSlider ( options, target ) { - - // Apply classes and data to the target. - target.addClass([ - Classes[0], - Classes[8 + options.dir], - Classes[4 + options.ort] - ].join(' ')); - - return $('
').appendTo(target).addClass( Classes[1] ); - } - - -// Slider scope - -function closure ( target, options, originalOptions ){ - -// Internal variables - - // All variables local to 'closure' are marked $. - var $Target = $(target), - $Locations = [-1, -1], - $Base, - $Serialization, - $Handles; - - // 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++ ){ - $Target.trigger(events[index], values); - } - } - - -// Handle placement - - // Test suggested values and apply margin, step. - function setHandle ( handle, to, delimit ) { - - var n = handle[0] !== $Handles[0][0] ? 1 : 0, - lower = $Locations[0] + options.margin, - upper = $Locations[1] - options.margin; - - // Don't delimit range dragging. - if ( delimit && $Handles.length > 1 ) { - to = n ? Math.max( to, lower ) : Math.min( to, upper ); - } - - // Handle the step option. - if ( to < 100 ){ - to = getStep(options, 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 falsy if handle can't move. False for 0 or 100 limit, - // '0' for limiting by another handle. - if ( to === $Locations[n] ) { - if ( $Handles.length === 1 ) { - return false; - } - return ( to === lower || to === upper ) ? 0 : 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[n] = to; - - // Invert the value if this is a right-to-left slider. - if ( options.dir ) { - to = 100 - to; - } - - // Write values to serialization Links. - // Convert the value to the correct relative representation. - // Convert the value to the slider stepping/range. - $($Serialization[n]).each(function(){ - this.write( fromStepping( options, to ), handle.children(), $Target ); - }); - - return true; - } - - // 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]; - } - - // Handles movement by tapping. - function jump ( handle, to, instant ) { - - if ( !instant ) { - // 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 ); - } - - // Move the handle to the new position. - setHandle( handle, to, false ); - - fireEvents(['slide', 'set', 'change']); - } - - -// Events - - // Handler for attaching events trough a proxy. - function attach ( events, element, callback, data ) { - - // 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 handle unset attributes differently. - var disabled = $Target.attr('disabled'); - disabled = !( disabled === undefined || disabled === null ); - - // Test if there is anything that should prevent an event - // from being handled, such as a disabled state or an active - // 'tap' transition. - if( $Target.hasClass( Classes[14] ) || disabled ) { - 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(); - - // Find the closest handle and calculate the tapped point. - // The set handle to the new position. - jump( $Handles[total], to, options.events.snap ); - - if ( options.events.snap ) { - start(event, { handles: [$Handles[total]] }); - } - } - - // Move handle to edges when target gets tapped. - function edge ( event ) { - - var i = event.calcPoint < $Base.offset()[ options.style ], - to = i ? 0 : 100; - - i = i ? 0 : $Handles.length - 1; - - jump( $Handles[i], to, false ); - } - - // 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++ ) { - - // 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 - }); - } - - // Extend tapping behaviour to target - if ( behaviour.extend ) { - - $Target.addClass( Classes[16] ); - - if ( behaviour.tap ) { - attach ( actions.start, $Target, edge, { - 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 - }); - } - } - - -// 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, $Target ); - $Handles = addHandles( options, $Base ); - $Serialization = addLinks( options, $Handles ); - - // Set the connect classes. - addConnection ( options.connect, $Target, $Handles ); - - // Attach user events. - events( options.events ); - - -// Methods - - // Set the slider value. - /** @expose */ - target.vSet = function ( ) { - - var args = Array.prototype.slice.call( arguments, 0 ), - callback, link, update, animate, - i, count, actual, to, values = asArray( args[0] ); - - // Extract modifiers for value method. - if ( typeof args[1] === 'object' ) { - callback = args[1]['set']; - link = args[1]['link']; - update = args[1]['update']; - animate = args[1]['animate']; - - // Support the 'true' option. - } else if ( args[1] === true ) { - callback = true; - } - - // 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. - if ( animate ) { - addClassFor( $Target, Classes[14], 300 ); - } - - // Determine how often to set the handles. - count = $Handles.length > 1 ? 3 : 1; - if ( values.length === 1 ) { - 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++ ) { - - to = link || $Serialization[i%2][0]; - to = to.getValue( values[i%2] ); - - if ( to === false ) { - continue; - } - - // Calculate the new handle position - to = toStepping( options, to ); - - // Invert the value if this is a right-to-left slider. - if ( options.dir ) { - to = 100 - to; - } - - // Force delimitation. - if ( setHandle( $Handles[i%2], to, true ) === true ) { - continue; - } - - // Reset the input if it doesn't match the slider. - $($Serialization[i%2]).each(function(index){ - - if (!index) { - actual = this.actual; - return true; - } - - this.write( - actual, - $Handles[i%2].children(), - $Target, - update - ); - }); - } - - // Optionally fire the 'set' event. - if( callback === true ) { - fireEvents(['set']); - } - - return this; - }; - - // Get the slider value. - /** @expose */ - target.vGet = function ( ) { - - var i, retour = []; - - // Get the value from all handles. - for ( i = 0; i < options.handles; i++ ){ - retour[i] = $Serialization[i][0].saved; - } - - // If only one handle is used, return a single value. - if ( retour.length === 1 ){ - return retour[0]; - } - - if ( options.dir ) { - return retour.reverse(); - } - - return retour; - }; - - // Destroy the slider and unbind all events. - /** @expose */ - target.destroy = function ( ) { - - // Loop all linked serialization objects and unbind all - // events in the noUiSlider namespace. - $.each($Serialization, function(){ - $.each(this, function(){ - // Won't remove 'change' when bound implicitly. - if ( this.target ) { - this.target.off( namespace ); - } - }); - }); - - // Unbind events on the slider, remove all classes and child elements. - $(this).off(namespace) - .removeClass(Classes.join(' ')) - .empty(); - - // Return the original options from the closure. - return originalOptions; - }; - - -// Value setting - - // Use the public value method to set the start values. - $Target.val( options.start ); -} - - -// Access points - - // 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 = test( 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(){ - - // 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 ); - - // 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 extects '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); - }); - }; - -// Remap the serialization constructor for legacy support. - /** @expose */ - $.noUiSlider = { 'Link': $.Link }; - -// Extend jQuery/Zepto with the noUiSlider method. - /** @expose */ - $.fn.noUiSlider = function ( options, re ) { - return ( re ? rebuild : initialize ).call(this, options); - }; - -}( window['jQuery'] || window['Zepto'] )); diff --git a/jquery.nouislider.min.js b/jquery.nouislider.min.js index b9a63edd..850b0b32 100644 --- a/jquery.nouislider.min.js +++ b/jquery.nouislider.min.js @@ -1,31 +1,22 @@ -/* - -$.Link (part of noUiSlider) - WTFPL */ -(function(c){function m(a,c,d){if((a[c]||a[d])&&a[c]===a[d])throw Error("(Link) '"+c+"' can't match '"+d+"'.'");}function r(a){void 0===a&&(a={});if("object"!==typeof a)throw Error("(Format) 'format' option must be an object.");var h={};c(u).each(function(c,n){if(void 0===a[n])h[n]=A[c];else if(typeof a[n]===typeof A[c]){if("decimals"===n&&(0>a[n]||7a&&(n=this.a("negative"),k=this.a("negativeBefore"));a=Math.abs(a).toFixed(d).toString();a=a.split(".");this.a("thousand")?(m=c(a[0]).match(/.{1,3}/g),m=c(m.join(c(this.a("thousand"))))):m=a[0];this.a("mark")&&1")[0]};k.prototype.H=function(a){this.method="val";this.j=document.createElement("input");this.j.name=a;this.j.type="hidden"};k.prototype.G=function(a){function h(a,c){return[c?null:a,c?a:null]}var d=this;this.method="val";this.target=a.on("change",function(a){d.B.val(h(c(a.target).val(),d.t),{link:d,set:!0})})};k.prototype.p=function(a,h,d,k){this.g=d;this.update=!k;if("string"=== -typeof a&&0===a.indexOf("-tooltip-"))this.K(a,h);else if("string"===typeof a&&0!==a.indexOf("-"))this.H(a);else if("function"===typeof a)this.target=!1,this.method=a;else{if(a instanceof c||c.zepto&&c.zepto.isZ(a)){if(!h){if(a.is("input, select, textarea")){this.G(a);return}h="html"}if("function"===typeof h||"string"===typeof h&&a[h]){this.method=h;this.target=a;return}}throw new RangeError("(Link) Invalid Link.");}};k.prototype.write=function(a,c,d,k){if(!this.update||!1!==k)if(this.u=a,this.F=a= -this.format(a),"function"===typeof this.method)this.method.call(this.target[0]||d[0],a,c,d);else this.target[this.method](a,c,d)};k.prototype.q=function(a){this.g=new r(c.extend({},a,this.g instanceof r?this.g.r:this.g))};k.prototype.J=function(a){this.B=a};k.prototype.I=function(a){this.t=a};k.prototype.format=function(a){return this.g.L(a)};k.prototype.A=function(a){return this.g.w(a)};k.prototype.p.prototype=k.prototype;c.Link=k})(window.jQuery||window.Zepto);/* - -$.fn.noUiSlider - WTFPL - refreshless.com/nouislider/ */ -(function(c){function m(e){return"number"===typeof e&&!isNaN(e)&&isFinite(e)}function r(e){return c.isArray(e)?e:[e]}function k(e,b){e.addClass(b);setTimeout(function(){e.removeClass(b)},300)}function u(e,b){return 100*b/(e[1]-e[0])}function A(e,b){if(b>=e.d.slice(-1)[0])return 100;for(var a=1,c,f,d;b>=e.d[a];)a++;c=e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return d+u(c,0>c[0]?b+Math.abs(c[0]):b-c[0])/(100/(e.c[a]-d))}function a(e,b){if(100<=b)return e.d.slice(-1)[0];for(var a=1,c,f,d;b>=e.c[a];)a++;c= -e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return 100/(e.c[a]-d)*(b-d)*(c[1]-c[0])/100+c[0]}function h(a,b){for(var c=1,g;(a.dir?100-b:b)>=a.c[c];)c++;if(a.m)return g=a.c[c-1],c=a.c[c],b-g>(c-g)/2?c:g;a.h[c-1]?(g=a.h[c-1],c=a.c[c-1]+Math.round((b-a.c[c-1])/g)*g):c=b;return c}function d(a,b){if(!m(b))throw Error("noUiSlider: 'step' is not numeric.");a.h[0]=b}function n(a,b){if("object"!==typeof b||c.isArray(b))throw Error("noUiSlider: 'range' is not an object.");if(void 0===b.min||void 0===b.max)throw Error("noUiSlider: Missing 'min' or 'max' in 'range'."); -c.each(b,function(b,g){var d;"number"===typeof g&&(g=[g]);if(!c.isArray(g))throw Error("noUiSlider: 'range' contains invalid value.");d="min"===b?0:"max"===b?100:parseFloat(b);if(!m(d)||!m(g[0]))throw Error("noUiSlider: 'range' value isn't numeric.");a.c.push(d);a.d.push(g[0]);d?a.h.push(isNaN(g[1])?!1:g[1]):isNaN(g[1])||(a.h[0]=g[1])});c.each(a.h,function(b,c){if(!c)return!0;a.h[b]=u([a.d[b],a.d[b+1]],c)/(100/(a.c[b+1]-a.c[b]))})}function E(a,b){"number"===typeof b&&(b=[b]);if(!c.isArray(b)||!b.length|| -2
").addClass(f[2]),g=["-lower","-upper"];a.dir&&g.reverse();d.children().addClass(f[3]+" "+f[3]+g[b]);return d}function Q(a,b){b.j&&(b=new c.Link({target:c(b.j).clone().appendTo(a),method:b.method,format:b.g},!0));return b}function R(a,b){var d,f=[];for(d=0;d").appendTo(b).addClass(f[1])}function V(d,b,m){function g(){return t[["width","height"][b.k]]()}function n(a){var b,c=[q.val()];for(b=0;bp&&(p=h(b,p));p=Math.max(Math.min(parseFloat(p.toFixed(7)),100),0);if(p===x[g])return 1===l.length?!1:p===H||p===k?0:!1;d.css(b.style,p+"%");d.is(":first-child")&&d.toggleClass(f[17],50d&&(e+=Math.abs(d)),100=g[e];)e+=1;return e}function k(d,g,e,a){this.xPct=[];this.xVal=[];this.xSteps=[a||!1];this.xNumSteps=[!1];this.snap=g;this.direction=e;for(var f in d)if(d.hasOwnProperty(f)){g=f;e=d[f];a=void 0;"number"===typeof e&&(e=[e]);if("[object Array]"!==Object.prototype.toString.call(e))throw Error("noUiSlider: 'range' contains invalid value.");a="min"===g?0: +"max"===g?100:parseFloat(g);if(!q(a)||!q(e[0]))throw Error("noUiSlider: 'range' value isn't numeric.");this.xPct.push(a);this.xVal.push(e[0]);a?this.xSteps.push(isNaN(e[1])?!1:e[1]):isNaN(e[1])||(this.xSteps[0]=e[1])}this.xNumSteps=this.xSteps.slice(0);for(f in this.xNumSteps)this.xNumSteps.hasOwnProperty(f)&&(d=Number(f),(g=this.xNumSteps[f])&&(this.xSteps[d]=s([this.xVal[d],this.xVal[d+1]],g)/(100/(this.xPct[d+1]-this.xPct[d]))))}k.prototype.getMargin=function(d){return 2===this.xPct.length?s(this.xVal, +d):!1};k.prototype.toStepping=function(d){var g=this.xVal,a=this.xPct;if(d>=g.slice(-1)[0])d=100;else{var m=p(d,g),f,k;f=g[m-1];k=g[m];g=a[m-1];a=a[m];f=[f,k];d=s(f,0>f[0]?d+Math.abs(f[0]):d-f[0]);d=g+d/(100/(a-g))}this.direction&&(d=100-d);return d};k.prototype.fromStepping=function(d){this.direction&&(d=100-d);var a;var e=this.xVal;a=this.xPct;if(100<=d)a=e.slice(-1)[0];else{var m=p(d,a),f,k;f=e[m-1];k=e[m];e=a[m-1];f=[f,k];a=100/(a[m]-e)*(d-e)*(f[1]-f[0])/100+f[0]}d=Math.pow(10,7);return Number((Math.round(a* +d)/d).toFixed(7))};k.prototype.getStep=function(d){this.direction&&(d=100-d);var a=this.xPct,e=this.xSteps,k=this.snap;if(100!==d){var f=p(d,a);k?(e=a[f-1],a=a[f],d=d-e>(a-e)/2?a:e):(e[f-1]?(k=a[f-1],e=e[f-1],a=Math.round((d-a[f-1])/e)*e,a=k+a):a=d,d=a)}this.direction&&(d=100-d);return d};k.prototype.getApplicableStep=function(a){var g=p(a,this.xPct);a=100===a?2:1;return[this.xNumSteps[g-2],this.xVal[g-a],this.xNumSteps[g-a]]};k.prototype.convert=function(a){return this.getStep(this.toStepping(a))}; +a.noUiSlider={Spectrum:k}})(window.jQuery||window.Zepto);(function(a){function q(c){return"number"===typeof c&&!isNaN(c)&&isFinite(c)}function s(c,b){if(!q(b))throw Error("noUiSlider: 'step' is not numeric.");c.singleStep=b}function p(c,b){if("object"!==typeof b||a.isArray(b))throw Error("noUiSlider: 'range' is not an object.");if(void 0===b.min||void 0===b.max)throw Error("noUiSlider: Missing 'min' or 'max' in 'range'.");c.spectrum=new a.noUiSlider.Spectrum(b,c.snap,c.dir,c.singleStep)}function k(c,b){var d=b;b=a.isArray(d)?d:[d];if(!a.isArray(b)||!b.length|| +2
").addClass(h[2]),e=["-lower","-upper"];c&&e.reverse();d.children().addClass(h[3]+" "+h[3]+e[b]);return d}function k(a,b,d){switch(a){case 1:b.addClass(h[7]);d[0].addClass(h[6]);break;case 3:d[1].addClass(h[6]);case 2:d[0].addClass(h[7]);case 0:b.addClass(h[6])}}function d(a,b,d){var e,f=[];for(e=0;e").appendTo(d).addClass(h[1])}function e(c,b,e){function f(){return v[["width","height"][b.ort]]()}function m(b){var a,c=[n.val()];for(a=0;ad&&(e+=Math.abs(d)),100=c[1]?c[2]:c[0],c[2]]]});return p(b)};c.getInfo=function(){return[z,b.style,b.ort]};n.val(b.start)}function m(c){if(!this.length)throw Error("noUiSlider: Can't initialize slider on empty selection.");var b=a.noUiSlider.testOptions(c,this);return this.each(function(){e(this, +b,c)})}function f(c){return this.each(function(){if(this.destroy){var b=a(this).val(),d=this.destroy(),e=a.extend({},d,c);a(this).noUiSlider(e);this.reappend();d.start===e.start&&a(this).val(b)}else a(this).noUiSlider(c)})}function x(){return this[0][arguments.length?"vSet":"vGet"].apply(this[0],arguments)}var y=a(document),t=a.fn.val,u=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"},h="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".split(" ");a.fn.val=function(){var c=arguments,b=a(this[0]);return arguments.length?this.each(function(){(a(this).hasClass(h[0])?x:t).apply(a(this),c)}):(b.hasClass(h[0])?x:t).call(b)};a.fn.noUiSlider= +function(a,b){return(b?f:m).call(this,a)}})(window.jQuery||window.Zepto); diff --git a/jquery.nouislider.pips.css b/jquery.nouislider.pips.css new file mode 100644 index 00000000..d1a93971 --- /dev/null +++ b/jquery.nouislider.pips.css @@ -0,0 +1,98 @@ + +/* Base; + * + */ +.noUi-pips, +.noUi-pips * { +-moz-box-sizing: border-box; + box-sizing: border-box; +} +.noUi-pips { + position: absolute; + font: 400 12px Arial; + color: #999; +} + +/* Values; + * + */ +.noUi-value { + width: 40px; + position: absolute; + text-align: center; +} +.noUi-value-sub { + color: #ccc; + font-size: 10px; +} + +/* Markings; + * + */ +.noUi-marker { + position: absolute; + background: #CCC; +} +.noUi-marker-sub { + background: #AAA; +} +.noUi-marker-large { + background: #AAA; +} + +/* Horizontal layout; + * + */ +.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; +} + +/* Vertical layout; + * + */ +.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; +} diff --git a/libLink b/libLink new file mode 160000 index 00000000..e6a1ddf4 --- /dev/null +++ b/libLink @@ -0,0 +1 @@ +Subproject commit e6a1ddf4bd411e9f793db80c8692314783d28200 diff --git a/src/module.base.js b/src/module.base.js new file mode 100644 index 00000000..b71a9c1e --- /dev/null +++ b/src/module.base.js @@ -0,0 +1,872 @@ +/*jslint browser: true */ +/*jslint white: true */ + +(function( $ ){ + + 'use strict'; + + 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' + ]; + + +// General helpers + + // 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]; + } + + +// Class handling + + // Sets a class and removes it after [duration] ms. + function addClassFor ( element, className, duration ) { + element.addClass(className); + setTimeout(function(){ + element.removeClass(className); + }, duration); + } + + // 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] ); + } + + +// Slider scope + +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. + if ( options.dir ) { + 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; + + + // 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]); + } + } + } + } + + + + // 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 + }); + } + } + + + + // 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 ); + } + + + +// 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.getInfo = function(){ + return [ + $Spectrum, + options.style, + options.ort + ]; + }; + + // Use the public value method to set the start values. + $Target.val( options.start ); +} + + +// Access points + + // 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 = $.noUiSlider.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 ) { + return ( rebuildFlag ? rebuild : initialize ).call(this, options); + }; + +}( window.jQuery || window.Zepto )); diff --git a/src/module.options.js b/src/module.options.js new file mode 100644 index 00000000..20d64aa8 --- /dev/null +++ b/src/module.options.js @@ -0,0 +1,269 @@ +/*jslint browser: true */ +/*jslint white: true */ + +/* Every input option is tested and parsed. This'll prevent + endless validation in internal methods. These tests are + structured with an item for every option available. An + option can be marked as required by setting the 'r' flag. + The testing function is provided with three arguments: + - The provided value for the option; + - A reference to the options object; + - The name for the option; + + The testing function returns false when an error is detected, + or true when everything is OK. It can also modify the option + object, to make sure all values can be correctly looped elsewhere. */ + +(function( $ ){ + + 'use strict'; + + // Wraps a variable as an array, if it isn't one yet. + function asArray ( a ) { + return $.isArray(a) ? a : [a]; + } + + // Checks whether a value is numerical. + function isNumeric ( a ) { + return typeof a === 'number' && !isNaN( a ) && isFinite( a ); + } + + /** @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 $.noUiSlider.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."); + } + + 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; + } + + // Test all developer settings and parse to assumption-safe values. + $.noUiSlider.testOptions = testOptions; + +}( window.jQuery || window.Zepto )); diff --git a/src/module.range.js b/src/module.range.js new file mode 100644 index 00000000..9c44754d --- /dev/null +++ b/src/module.range.js @@ -0,0 +1,290 @@ +/*jslint browser: true */ +/*jslint white: true */ + +(function( $ ){ + + 'use strict'; + +// Helpers + + // Determine the size of a sub-range in relation to a full range. + function subRangeRatio ( pa, pb ) { + return (100 / (pb - pa)); + } + + // 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)); + } + + +// Value calculation + + // (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)); + }; + + $.noUiSlider = { + Spectrum: Spectrum + }; + +}( window.jQuery || window.Zepto )); diff --git a/src/optional.pips.js b/src/optional.pips.js new file mode 100644 index 00000000..a7c862d0 --- /dev/null +++ b/src/optional.pips.js @@ -0,0 +1,234 @@ +/*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); + }); + } + +// Pips + + 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 ) { + + 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('
' + Math.round(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, + 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 + )); + }); + }; + +}( window.jQuery || window.Zepto )); diff --git a/tests/addon_pips.js b/tests/addon_pips.js new file mode 100644 index 00000000..dac94735 --- /dev/null +++ b/tests/addon_pips.js @@ -0,0 +1,164 @@ + + function filter500( value, type ){ + return value%1000 ? 2 : 1; + } + + function test_slider(){ + + Q.html('\ +
\ + '); + + return Q.find("#slider").noUiSlider({ + range: { + 'min': [ 0 ], + '10%': [ 500, 500 ], + '50%': [ 4000, 1000 ], + 'max': [ 10000 ] + }, + start: 0 + }) + } + + // RANGE + + test( "Range", function(){ + + var slider = test_slider(); + + slider.noUiSlider_pips({ + mode: 'range', + density: 3 + }); + + ok( Q.find('.noUi-pips'), 'Pips where created' ); + + var markers = Q.find('.noUi-marker').length; + ok( markers >= 32 && markers <= 34, 'Density of 1/3 was applied' ); + + }); + + test( "Steps", function(){ + + var slider = test_slider(); + + // STEPS + + slider.noUiSlider_pips({ + mode: 'steps', + density: 2, + filter: filter500 + }); + + var markers = Q.find('.noUi-marker').length; + ok( markers >= 49 && markers <= 51, 'Density of 1/2 was applied' ); + + }); + + test( "Positions", function(){ + + var slider = test_slider(); + + // POSITIONS + + slider.noUiSlider_pips({ + mode: 'positions', + values: [0,25,50,75,100] + }); + + equal( Q.find('.noUi-marker-large').length, 5, 'Large markers added for all values' ); + equal( Q.find('.noUi-value').length, 5 ); + + var pos = []; + Q.find('.noUi-value').each(function(){ + pos.push(parseInt(this.style.left)); + }); + + deepEqual(pos, [0,25,50,75,100], 'Values placed on proper positions'); + + }); + + test( "Positions, stepped", function(){ + + expect(0); // TODO + + var slider = test_slider(); + + // POSITIONS (STEPPED) + + slider.noUiSlider_pips({ + mode: 'positions', + values: [0,25,50,75,100], + stepped: true + }); + + }); + + test( "Count", function(){ + + var slider = test_slider(); + + // COUNT + + slider.noUiSlider_pips({ + mode: 'count', + values: 8 + }); + + equal( Q.find('.noUi-value').length, 8, 'Placed requested number of values' ); + + var pos2 = []; + Q.find('.noUi-value').each(function(){ + pos2.push(parseInt(this.style.left)); + }); + + deepEqual(pos2, [0, Math.floor((100/7)*1), Math.floor((100/7)*2), Math.floor((100/7)*3), Math.floor((100/7)*4), Math.floor((100/7)*5), Math.floor((100/7)*6), 100], 'Values spread evenly'); + + }); + + test( "Count, stepped", function(){ + + expect(0); // TODO + + var slider = test_slider(); + + // COUNT (STEPPED) + + slider.noUiSlider_pips({ + mode: 'count', + values: 8, + stepped: true + }); + + }); + + // VALUES + + test( "Values", function(){ + + var slider = test_slider(); + + slider.noUiSlider_pips({ + mode: 'values', + values: [50, 552, 750, 940, 5000, 6080, 9000] + }); + + equal( Q.find('.noUi-value').length, 7, 'Placed requested number of values' ); + + }); + + // VALUES (STEPPED) + + test( "Values, stepped", function(){ + + var slider = test_slider(); + + slider.noUiSlider_pips({ + mode: 'values', + values: [50, 552, 750, 940, 5000, 6080, 9000], + stepped: true + }); + + equal( Q.find('.noUi-value').length, 6, 'Removed duplicate in step' ); + + }); diff --git a/tests/errors.js b/tests/errors.js deleted file mode 100644 index 41674fa1..00000000 --- a/tests/errors.js +++ /dev/null @@ -1,69 +0,0 @@ - - test( "Testing input validation.", function(){ - - Q.html('\ -
\ -
\ -
\ -
\ - '); - - var settings = { - start: [10,30], - connect: true, - range: { - 'min': -20, - 'max': 40 - } - }; - - throws(function(){ - $('.slider1').noUiSlider($.extend({}, { - serialization: { - lower: [ - new Link({ - target: input, - format: { - decimals: 8 - } - }) - ] - } - }, settings)); - }, "Decimal count."); - - throws(function(){ - $('.slider2').noUiSlider($.extend({}, { - serialization: { - format: { - negative: function(){} - } - } - }, settings)); - }, "Format item type."); - - throws(function(){ - $('.slider3').noUiSlider($.extend({}, { - serialization: { - lower: [ - new Link({ - target: function(){}, - format: { - mark: ',' - } - }) - ], - format: { - thousand: ',' - } - } - }, settings)); - }, "Incompatible equal formatting options."); - - throws(function(){ - $('.slider4').noUiSlider({ - range: { }, - start: [20, 30] - }); - }, "Missing 'min' or 'max' in range."); - }); diff --git a/tests/events.js b/tests/events.js deleted file mode 100644 index 7b898256..00000000 --- a/tests/events.js +++ /dev/null @@ -1,61 +0,0 @@ - - test( "Triggering events", function(){ - - Q.html('\ -
\ - '); - - var slider = $('.slider'); - - slider.noUiSlider({ - start: [ 30, 40 ], - connect: true, - range: { - 'min': [ 50 ], - 'max': [ 90 ] - }, - serialization: { - format: { - decimals: 0, - postfix: ',-' - } - } - }); - - var w = slider.width(); - - var handles = slider.find('.noUi-handle'); - - slider.on("slide", function( event, values ){ - deepEqual( values, ['50,-', '70,-'], "Slide event has proper parameters." ); - }); - - handles.first().trigger($.Event('mousedown', { - clientX: 10, - clientY: 0 - })); - - $(document).trigger($.Event('mousemove', { - clientX: 52, - clientY: 0 - })); - - $(document).trigger('mouseup'); - - deepEqual( slider.val(), ['50,-', '50,-'], "Slider didn't cross." ); - - handles.last().trigger($.Event('mousedown', { - clientX: 10, - clientY: 0 - })); - - $(document).trigger($.Event('mousemove', { - clientX: 130, - clientY: 0 - })); - - $(document).trigger('mouseup'); - - deepEqual( slider.val(), ['50,-', '70,-'], "Slider moved properly." ); - - }); diff --git a/tests/format.js b/tests/format.js deleted file mode 100644 index bd77cd6a..00000000 --- a/tests/format.js +++ /dev/null @@ -1,82 +0,0 @@ - - test( "Serialization and formatting options", function(){ - - Q.html('\ -
\ - \ - \ - \ - '); - - $("#slider").noUiSlider({ - range: { - 'min': [ 0.001 ], - 'max': [ 0.003 ] - } - ,start: [ 0, 0.002 ] - ,connect: true - ,behaviour: 'drag' - ,serialization: { - lower: [ - - new Link({ - target: '-tooltip-
', - method: function( value, handle, slider ){ - ok( $("#slider")[0] === slider[0] ); - ok( handle.hasClass('noUi-handle') ); - }, - format: { - decimals: 6 - ,prefix: '#' - ,negativeBefore: '!' - ,negative: '' - } - }), - - new Link({ - target: $("#inputChange"), - format: { - mark: '.' - ,thousand: ',' - ,prefix: '$' - ,postfix: ' p.p.' - ,decimals: 3 - ,negativeBefore: '-' - ,negative: '' - ,encoder: function( a ){ - return a * 1E7; - } - ,decoder: function( a ){ - return a / 1E7; - } - } - }) - ] - ,upper: [ - - new Link({ - target: $("#inputVal"), - method: 'val' - }) - ] - ,format: { - decimals: 5 - } - } - }); - - $("#inputChange").val( "15000" ).trigger('change'); - - equal( $("#inputChange").val(), "$15,000.000 p.p.", "Testing interpretation of improper data" ); - - $("#inputVal").val( 3 ); - - equal( $("#inputVal").val(), "3" ); - - $("#inputVal").trigger("change"); - - equal ( $("#inputVal").val(), "3", "Input doesn't respond to change events." ); - - deepEqual( $("#slider").val(), ["0.00150", "0.00200"] ); - - }); diff --git a/tests/default-val.js b/tests/general_val.js similarity index 91% rename from tests/default-val.js rename to tests/general_val.js index e64a4d78..7e456382 100644 --- a/tests/default-val.js +++ b/tests/general_val.js @@ -1,5 +1,5 @@ - test( "Value setting/getting on normal inputs", function(){ + test( "Make sure .val() behaviour didn't change.", function(){ Q.html('\ \ diff --git a/tests/link_basic.js b/tests/link_basic.js new file mode 100644 index 00000000..5c7b028c --- /dev/null +++ b/tests/link_basic.js @@ -0,0 +1,90 @@ + + test( "Link implementation", function(){ + + $.fn.customMethod = function(val){ + this.html(val); + }; + + Q.html('\ +
\ + \ +
\ +
\ + '); + + var slider = $('.slider'), input = $("#input-field"), div1 = $('#div-field1'), div2 = $('#div-field2'); + + throws(function(){ + + slider.Link().to('throws'); + + }, 'Can\'t link before slider.'); + + slider.noUiSlider({ + start: [ 0, 20 ], + connect: true, + step: 10, + range: { + 'min': -30, + 'max': 30 + }, + format: TEST_ROUND_FORMAT + }); + + deepEqual(slider.val(), ['0', '20']); + + slider.Link().to(input); + + equal(input.val(), '0', 'Slider value is written to input'); + + input.val(60).change(); + equal(input.val(), '20', 'Value was parsed and limited to handle max.'); + deepEqual(slider.val(), ['20', '20']); + + slider.val([null, 30]); + deepEqual(slider.val(), ['20', '30']); + + input.val(60).change(); + equal(input.val(), '30', 'Value was parsed and limited to slider max.'); + + input.val(23).change(); + input.val('Run, Forrest!').change(); + equal(input.val(), '20', 'Slider ignored invalid value and reset input.'); + + slider.Link(false); + slider.val([-20, 10]) + + equal(input.val(), '20', 'Input ignored the slider'); + + input.val(-20).change(); + deepEqual(slider.val(), ['-20', '10'], 'Input didn\t affect slider.'); + + slider.Link('upper').to(div1, 'customMethod'); + + equal(div1.html(), '10', 'HTML was set'); + + slider.Link('upper').to(div2, function( value, handle, sliderInstance ){ + + equal(value, 10, "Value as expected."); + + ok(handle.hasClass("noUi-handle"), "Handle is a handle."); + ok(sliderInstance.hasClass("noUi-target"), "Slider is a slider."); + ok(div2[0] === this[0], "Element is scope of call and $."); + ok(slider[0] === sliderInstance[0], "Slider argument is slider."); + + $(this).Link(false); + }); + + equal(div2.html(), '', 'Element wasn\t altered.'); + + slider.Link().to(function( value, handle, sliderInstance ){ + + equal(value, -20, "Value as expected."); + + ok(handle.hasClass("noUi-handle-lower"), "Handle is a handle."); + ok(sliderInstance.hasClass("noUi-target"), "Slider is a slider."); + ok(sliderInstance[0] === this[0], "Slider is scope of call and $."); + + $(this).Link(false); + }); + }); diff --git a/tests/inputs.js b/tests/link_multiple.js similarity index 75% rename from tests/inputs.js rename to tests/link_multiple.js index db1cfa61..8643a9c3 100644 --- a/tests/inputs.js +++ b/tests/link_multiple.js @@ -9,26 +9,17 @@ \ '); - Q.find(".slider").noUiSlider({ - range: { + Q.find('.slider').noUiSlider({ + start: [ 20, 80 ], + range: { 'min': [ 0 ], 'max': [ 100 ] } - ,start: [ 20, 80 ] - ,serialization: { - lower: [ - new Link({ - target: 'lower' - }) - ] - ,upper: [ - new Link({ - target: 'upper' - }) - ] - } }); + Q.find('.slider').Link('lower').to('input-lower'); + Q.find('.slider').Link('upper').to('input-upper'); + $("#slidera").val([50, 61]); $("#sliderb").val([51, 62]); $("#sliderc").val([52, 63]); @@ -44,4 +35,6 @@ equal( $(inputs[4]).val(), '52.00' ); equal( $(inputs[5]).val(), '63.00' ); + equal(Q.find('[name="input-lower"]').length, 3); + }); diff --git a/tests/link_select.js b/tests/link_select.js new file mode 100644 index 00000000..9ec5fd67 --- /dev/null +++ b/tests/link_select.js @@ -0,0 +1,32 @@ + + test( "Testing select elements", function(){ + + Q.html('\ +
\ + \ + '); + + var select = $('#select'); + + for( var i = -20; i <= 40; i++ ){ + select.append( + '' + ); + } + + $('.slider').noUiSlider({ + start: [ 10, 30 ], + connect: true, + range: { + 'min': -20, + 'max': 40 + } + }); + + $('.slider').Link('lower').to(select, null, TEST_ROUND_FORMAT); + + select.val( 40 ).change(); + + equal( select.val(), '30', 'Select was reset properly.' ); + + }); diff --git a/tests/link_update.js b/tests/link_update.js new file mode 100644 index 00000000..c54b750c --- /dev/null +++ b/tests/link_update.js @@ -0,0 +1,68 @@ + + test( "Test re-append of inline elements", function(){ + + Q.html('\ +
\ +
\ + \ +
\ + '); + + var slider = Q.find('.slider'), input = Q.find('#input-element'); + + slider.noUiSlider({ + start: [50, 70], + direction: 'rtl', + range: { + 'min': [ 0 ], + 'max': [ 100 ] + } + }); + + slider.Link().to('input-lower'); + slider.Link('upper').to(input); + + equal(input.val(), '70.00'); + equal(Q.find('[name="input-lower"]').length, 1); + equal(Q.find('[name="input-lower"]').val(), '50.00'); + + slider.Link(false); + equal(Q.find('[name="input-lower"]').length, 0, 'input was removed.'); + + slider[0].destroy(); + + throws(function(){ + slider.Link().to('input-throws'); + }, 'No slider to link to.'); + + slider.noUiSlider({ + start: [30, 40], + range: { + 'min': [ 0 ], + 'max': [ 150 ] + } + }); + + equal(input.val(), '70.00', 'Input didn\t change.'); + input.val(30).change(); + + deepEqual(slider.val(), ['30.00', '40.00'], 'Slider didn\'t change.'); + + slider.Link('lower').to('input-new'); + slider.Link('upper').to('-inline-'); + + equal(Q.find('[name="input-new"]').length, 1); + equal(Q.find('.noUi-handle > div').length, 1, 'Inline element was appended'); + equal(Q.find('.noUi-handle > div').html(), '40.00'); + + slider.noUiSlider({ + start: [40, 90], + direction: 'rtl' + }, true); + + equal(Q.find('.noUi-handle > div').html(), '90.00', 'Element is still there, with new value.'); + + equal(Q.find('[name="input-new"]').length, 1, 'Input was re-appended'); + equal(Q.find('[name="input-new"]').val(), '40.00', 'Value was set.'); + + }); diff --git a/tests/links.js b/tests/links.js deleted file mode 100644 index 3a693e99..00000000 --- a/tests/links.js +++ /dev/null @@ -1,94 +0,0 @@ - - test( "Testing Links", function(){ - - Q.html('\ -
\ -
\ -
\ -
\ - '); - - $.fn.cake = function(val){ - this.html(val); - }; - - var val1 = 0, - val2 = 0, - vals = ['-100.00', '-100.00', '-90.00', '-90.00']; - - var slider = $('.slider'), - box = $('.box'), - item = $('.item'), - thing = $('.thing'); - - function linkTargetFunction( value, handle, sliderInstance ){ - equal(value, vals[val1++], "Value as expected, change ("+val1+"/4)."); - ok(handle.hasClass("noUi-handle"), "Handle is really a handle."); - ok(handle.hasClass("noUi-handle-lower"), "Handle is really the lower handle."); - ok(sliderInstance.hasClass("noUi-target"), "Slider is really a slider."); - ok(sliderInstance[0] === this, "Slider is scope of call."); - } - - function linkMethodFunction( value, handle, sliderInstance ){ - equal(value, vals[val2++], "Value as expected, change ("+val2+"/4)."); - ok(handle.hasClass("noUi-handle"), "Handle is really a handle."); - ok(sliderInstance.hasClass("noUi-target"), "Slider is really a slider."); - ok(box[0] === this, "Element is scope of call."); - ok(slider[0] === sliderInstance[0], "Slider argument is slider."); - } - - slider.noUiSlider({ - range: { - 'min': -100, - 'max': 9000 - } - ,start: [-500, 10000] - ,connect: true - ,serialization: { - lower: [ - new Link({ - target: linkTargetFunction - }), - new Link({ - target: box, - method: linkMethodFunction - }), - new Link({ - target: "hiddenInputField", - format: { - decimals: 1, - negative: '', - prefix: '++', - negativeBefore: '=' - } - }) - ], - upper: [ - new Link({ - target: item, - method: "cake" - }), - new Link({ - target: thing, - method: "html" - }) - ] - } - }); - - equal(item.html(), '9000.00', 'Setting by custom method works.'); - - ok($('input[name="hiddenInputField"]').is('input'), "Input field was generated."); - ok($('input[name="hiddenInputField"]').val(), '=++90.0', "Value is formatted as expected."); - - - slider.val([-90, 8051]); - - deepEqual(slider.val(), ['-90.00', '8051.00']); - - // Link updated 4 times. - equal(val1, 4); - - equal(thing.html(), '8051.00', 'Setting by existing method works.'); - - }); diff --git a/tests/range.html b/tests/range.html new file mode 100644 index 00000000..d3e91f97 --- /dev/null +++ b/tests/range.html @@ -0,0 +1,71 @@ + + + + Testing + + + + + + + +
+ + + diff --git a/tests/rtl.js b/tests/rtl.js deleted file mode 100644 index cc669589..00000000 --- a/tests/rtl.js +++ /dev/null @@ -1,78 +0,0 @@ - - test( "RTL value method", function(){ - - Q.html('\ -
\ -
\ - 0\ - 0\ -
\ - '); - - var slider = $("#slider").noUiSlider({ - range: { - 'min': 0, - 'max': 50 - }, - start: [10, 40], - behaviour: 'tap', - connect: true, - direction : 'rtl', - serialization: { - lower: [ - new Link({ - target: $("#min") - }) - ], - upper: [ - new Link({ - target: $("#max") - }) - ] - } - }); - deepEqual(slider.val(), ["10.00", "40.00"], "Proper start handling in RTL."); - - slider.val([20,30]); - deepEqual(slider.val(), ["20.00", "30.00"]); - - slider.val([40,45]); - deepEqual(slider.val(), ["40.00", "45.00"], "RTL slider overstepped properly."); - - slider.val([30,35]); - deepEqual(slider.val(), ["30.00", "35.00"], "RTL slider understepped properly."); - - }); - - test( "RTL slider multiple value set.", function(){ - - Q.html('\ -
\ -
\ - 0\ -
\ - '); - - var slider = $("#slider").noUiSlider({ - range: {min: 0.201, max: 1}, - step: 0.01, - start: [0.401], - direction: "rtl", - orientation: "vertical", - serialization: { - lower: [ - new Link({ - target: $("#min") - }) - ] - } - }); - - equal(slider.val(), 0.4); - - slider.val(0.201, true); - equal(slider.val(), 0.2); - - slider.val(0.201, true); - equal(slider.val(), 0.2); - }); diff --git a/tests/run.html b/tests/run.html deleted file mode 100644 index 7972ce3a..00000000 --- a/tests/run.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - Testing - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - diff --git a/tests/select.js b/tests/select.js deleted file mode 100644 index 5132474d..00000000 --- a/tests/select.js +++ /dev/null @@ -1,40 +0,0 @@ - - test( "Testing select elements", function(){ - - Q.html('\ -
\ - \ - '); - - var input = $('#input'); - - for( var i = -20; i <= 40; i++ ){ - input.append( - '' - ); - } - - $('.slider').noUiSlider({ - range: { - 'min': -20, - 'max': 40 - } - ,start: [10,30] - ,connect: true - ,serialization: { - lower: [ - new Link({ - target: input, - format: { - decimals: 0 - } - }) - ] - } - }); - - input.val( 40 ).change(); - - equal( input.val(), '30', 'Select was reset properly.' ); - - }); diff --git a/tests/set.js b/tests/set.js deleted file mode 100644 index ae924561..00000000 --- a/tests/set.js +++ /dev/null @@ -1,26 +0,0 @@ - - test( "Setting values", function(){ - - Q.html('\ -
\ - '); - - var slider = $('.slider'); - - slider.noUiSlider({ - range: { - 'min': 30, - 'max': 980 - } - ,start: [50,100] - ,connect: true - ,serialization: { - lower: [] - } - }); - - slider.val( [ 150, 600 ] ); - - deepEqual( slider.val(), ['150.00', '600.00'], 'Slider correctly overstepped limits.' ); - - }); diff --git a/tests/shared-link.js b/tests/shared-link.js deleted file mode 100644 index 2aecd614..00000000 --- a/tests/shared-link.js +++ /dev/null @@ -1,47 +0,0 @@ - - test( "Shared link element", function(){ - - Q.html('\ -
\ -
\ - \ - '); - - var span = $('#slider-span'); - - $("#slidera, #sliderb").noUiSlider({ - range: { - 'min': [ -20 ], - 'max': [ 20 ] - } - ,start: [ 0, 10 ] - ,behaviour: 'drag' - ,connect: true - ,serialization: { - lower: [ - - new Link({ - target: span, - format: { - mark: ',' - ,decimals: 1 - ,thousand: '.' - ,encoder: function( decoded ){ - return decoded * 10000; - } - ,decoder: function( encoded ){ - return encoded / 10000; - } - } - }) - - ] - } - }); - - $("#sliderb").val( 1 ); - - deepEqual( $("#slidera").val(), ['0.00', '10.00'] ); - equal( span.html(), '10.000,0' ); - - }); diff --git a/tests/slider.html b/tests/slider.html new file mode 100644 index 00000000..221b5ff1 --- /dev/null +++ b/tests/slider.html @@ -0,0 +1,73 @@ + + + + Testing + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/slider_events.js b/tests/slider_events.js new file mode 100644 index 00000000..75de2b46 --- /dev/null +++ b/tests/slider_events.js @@ -0,0 +1,109 @@ + + test( "Triggering events", function(){ + + Q.html('
'); + + var slider = Q.find('.slider'); + + slider.width(302); // Border of 2 pixels + + slider.noUiSlider({ + start: [ 0, 100 ], + connect: true, + range: { + 'min': [ 0 ], + 'max': [ 300 ] + } + }); + + equal(slider.find('.noUi-base').length, 1); + equal(slider.find('.noUi-base').width(), 300, 'Testable width') + + var handles = slider.find('.noUi-handle'); + + ok( !slider.hasClass('noUi-state-tap'), 'No transition on value set' ); + + slider.on('slide', function( event, values ){ + deepEqual( values, ['30.00', '100.00'], "Slide event has proper parameters." ); + slider.off('slide') + }); + + handles.first().trigger($.Event('mousedown', { + clientX: 10, + clientY: 0 + })); + + $(document).trigger($.Event('mousemove', { + clientX: 40, + clientY: 0 + })); + + $(document).trigger('mouseup'); + + + + + slider.on('set', function( event, values ){ + deepEqual( values, ['30.00', '249.00']); + slider.off('set') + }); + + handles.last().trigger($.Event('mousedown', { + clientX: 50, + clientY: 0 + })); + + $(document).trigger($.Event('mousemove', { + clientX: 100, + clientY: 0 + })); + + equal(handles.last().parent()[0].style.left, '50%', 'Proper half-way left offset.'); + + $(document).trigger($.Event('mousemove', { + clientX: 199, + clientY: 0 + })); + + $(document).trigger('mouseup'); + + deepEqual(slider.val(), ['30.00', '249.00'], 'Value has changed.'); + + + slider.val([10, 20]); + + ok( slider.hasClass('noUi-state-tap'), 'Animating class has been applied.' ); + + + QUnit.asyncTest( "Testing after animation", function(){ + + setTimeout(function() { + + QUnit.start(); + + ok( !slider.hasClass('noUi-state-tap'), 'Animation class is removed properly.' ); + + slider.on('change', function( event, values ){ + deepEqual( values, ['10.00', '10.00'], "Slide event has proper parameters." ); + slider.off('slide') + }); + + deepEqual( slider.val(), ['10.00', '20.00'] ); + + handles.last().trigger($.Event('mousedown', { + clientX: 1000, + clientY: 0 + })); + + $(document).trigger($.Event('mousemove', { + clientX: 980, + clientY: 0 + })); + + $(document).trigger('mouseup'); + + deepEqual( slider.val(), ['10.00', '10.00'], "Slider didn't cross." ); + + }, 350); + }); + }); diff --git a/tests/slider_limit.js b/tests/slider_limit.js new file mode 100644 index 00000000..9cc6651e --- /dev/null +++ b/tests/slider_limit.js @@ -0,0 +1,33 @@ + + test( "Limit option", function(){ + + Q.html('\ +
\ + '); + + var slider = Q.find('.slider'); + + slider.noUiSlider({ + start: [ 50, 100 ], + limit: 30, + range: { + 'min': 30, + 'max': 986 + } + }); + + deepEqual( slider.val(), ['50.00', '80.00'], 'Limit applied on init.' ); + + slider.val( [ null, 600 ] ); + deepEqual( slider.val(), ['50.00', '80.00'], 'Handle can\'t leave limit.' ); + + slider.val( [ 150, 600 ] ); + deepEqual( slider.val(), ['150.00', '180.00'], 'Multiple set limit.' ); + + slider.noUiSlider({ direction: 'rtl' }, true); + + deepEqual( slider.val(), ['150.00', '180.00'], 'Re-init rtl keeps value.' ); + + slider.val( [ 120, 240 ] ); + deepEqual( slider.val(), ['120.00', '150.00'], 'RTL set.' ); + }); diff --git a/tests/non-linear-rtl.js b/tests/slider_non-linear-rtl.js similarity index 90% rename from tests/non-linear-rtl.js rename to tests/slider_non-linear-rtl.js index 66b795b0..a0dc2498 100644 --- a/tests/non-linear-rtl.js +++ b/tests/slider_non-linear-rtl.js @@ -18,9 +18,7 @@ }, start: 44, direction : 'rtl', - serialization: { - format: { decimals: 0 } - } + format: TEST_ROUND_FORMAT }); sliderLTR.noUiSlider({ @@ -31,9 +29,7 @@ 'max': 50 }, start: 44, - serialization: { - format: { decimals: 0 } - } + format: TEST_ROUND_FORMAT }); equal(sliderRTL.val(), '40', 'Start stepping on rtl works'); diff --git a/tests/slider_rtl.js b/tests/slider_rtl.js new file mode 100644 index 00000000..17839d34 --- /dev/null +++ b/tests/slider_rtl.js @@ -0,0 +1,54 @@ + + test( "RTL slider multiple value set.", function(){ + + Q.html('\ +
\ +
\ + '); + + var slider = $("#slider").noUiSlider({ + range: {min: 0.201, max: 1}, + step: 0.01, + start: 0.401, + direction: "rtl", + orientation: "vertical", + format: { + to: function(x){ + return x.toFixed(1); + }, + from: Number + } + }); + + equal(slider.val(), 0.4); + + slider.val(0.201, true); + equal(slider.val(), 0.2); + + slider.val(0.201, true); + equal(slider.val(), 0.2); + + + var slider2 = $("#slider2").noUiSlider({ + range: { + 'min': 0, + 'max': 50 + }, + start: [10, 40], + behaviour: 'tap', + connect: true, + direction : 'rtl' + }); + + deepEqual(slider2.val(), ["10.00", "40.00"], "Proper start handling in RTL."); + + slider2.val([20,30]); + deepEqual(slider2.val(), ["20.00", "30.00"]); + + slider2.val([40,45]); + deepEqual(slider2.val(), ["40.00", "45.00"], "RTL slider overstepped properly."); + + slider2.val([30,35]); + deepEqual(slider2.val(), ["30.00", "35.00"], "RTL slider understepped properly."); + + }); diff --git a/tests/val.js b/tests/slider_setting-getting.js similarity index 87% rename from tests/val.js rename to tests/slider_setting-getting.js index 0cd6a04e..f040cf1a 100644 --- a/tests/val.js +++ b/tests/slider_setting-getting.js @@ -1,5 +1,5 @@ - test( "Testing val setting", function(){ + test( "Value setting/getting", function(){ Q.html('\
\ @@ -10,10 +10,11 @@ start: [ 0, 10 ], behaviour: 'drag', connect: true, - serialization: { - format: { - 'decimals': 1 - } + format: { + to: function(x){ + return x.toFixed(1); + }, + from: Number } }); diff --git a/tests/slider_step.js b/tests/slider_step.js new file mode 100644 index 00000000..6d860405 --- /dev/null +++ b/tests/slider_step.js @@ -0,0 +1,27 @@ + + test( "Testing handling of odd-numbered steps", function(){ + + Q.html('\ +
\ + '); + + var slider = $('.slider'); + + slider.noUiSlider({ + range: { min: 3, max: 106 }, + start: [ 20, 50 ], + step: 10, + format: TEST_ROUND_FORMAT + }); + + deepEqual( slider.val(), ['23', '53'] ); + + slider.val([50, 106]); + deepEqual( slider.val(), ['53', '106'], 'Slider reached edge outside from step.' ); + + slider.val([71, 105]); + deepEqual( slider.val(), ['73', '103'], 'Slider steps back into stepping with lower points as origin.' ); + + slider.val([71, 101]); + deepEqual( slider.val(), ['73', '103'] ); + }); diff --git a/tests/slider_update.js b/tests/slider_update.js new file mode 100644 index 00000000..67f78ae4 --- /dev/null +++ b/tests/slider_update.js @@ -0,0 +1,63 @@ + + test( "Testing update method", function(){ + + Q.html('\ +
\ + \ + '); + + var slider = $('.slider'); + + slider.noUiSlider({ + range: { min: 20, max: 140 }, + start: 50, + format: TEST_ROUND_FORMAT + }); + + deepEqual(slider.val(), '50'); + + slider[0].destroy(); + + ok(slider.is(':empty'), 'Slider was cleared'); + + equal ( slider.val(), '', 'Slider has no value' ); + + slider.noUiSlider({ + range: { min: 30, max: 70 }, + start: [ 30, 60 ], + format: TEST_ROUND_FORMAT + }); + + deepEqual(slider.val(), ['30', '60']); + + slider.val(70); + deepEqual(slider.val(), ['60', '60']); + + slider.val(40); + deepEqual(slider.val(), ['40', '60']); + + equal ( slider.find('.noUi-connect').length, 0, 'Slider uses no connection' ); + + slider.noUiSlider({ + connect: true + }, true); + + equal ( slider.find('.noUi-connect').length, 1, 'Slider now connects' ); + + deepEqual(slider.val(), ['40', '60'], 'Value was unchanged'); + + slider.val([30,50]); + deepEqual(slider.val(), ['30', '50'], 'Can still set slider'); + + slider[0].destroy(); + + ok ( !slider[0].destroy, 'destroy method removed itself'); + + slider.noUiSlider({ + start: 30, + range: { min: 80, max: 1000 } + }, true); + + equal(slider.val(), '80.00', 'Slider was build, ignoring the rebuild flag.'); + + }); diff --git a/tests/slider_values.js b/tests/slider_values.js new file mode 100644 index 00000000..1d9d019c --- /dev/null +++ b/tests/slider_values.js @@ -0,0 +1,26 @@ + + test( "Values", function(){ + + Q.html('\ +
\ + '); + + var slider = $('.slider'); + + slider.noUiSlider({ + start: [ 50, 100 ], + connect: true, + range: { + 'min': 30, + 'max': 986 + }, + format: TEST_ROUND_FORMAT + }); + + deepEqual( slider.val(), ['50', '100'], 'Values where set' ); + + slider.val( [ 150, 600 ] ); + + deepEqual( slider.val(), ['150', '600'], 'Slider correctly overstepped limits.' ); + + }); diff --git a/tests/update-link.js b/tests/update-link.js deleted file mode 100644 index 73f59cc3..00000000 --- a/tests/update-link.js +++ /dev/null @@ -1,58 +0,0 @@ - - test( "Testing update method", function(){ - - Q.html('\ -
\ - \ - \ - '); - - var slider = $("#slider"), - input1 = $("#input1"), - input2 = $("#input2"); - - var itemCustom = $.Link({ - target: $('#input1'), - format: { - prefix: '$', - mark: ',', - decimals: 3, - to: function( value ) { - return 'Hi! ' + value; - }, - from: function( value ) { - return value.slice(4); - } - } - }); - - var itemInherit = $.Link({ - target: $('#input2') - }); - - slider.noUiSlider({ - range: { min: 30, max: 120 }, - start: [ 50, 100 ], - serialization: { - lower: [ itemCustom ], - upper: [ itemInherit ], - format: { - prefix: '€', - mark: '*' - } - } - }); - - deepEqual(slider.val(), ["€50*00", "€100*00"]); - equal(input1.val(), "Hi! $50,000"); - equal(input2.val(), "€100*00"); - - slider.val([32, 118]); - deepEqual(slider.val(), ["€32*00", "€118*00"]); - - input1.val("Hi! $60,000").change(); - deepEqual(slider.val(), ["€60*00", "€118*00"]); - - slider.noUiSlider({ range: { min: 20, max: 130 } }, true); - deepEqual(slider.val(), ["€60*00", "€118*00"]); - }); diff --git a/tests/update.js b/tests/update.js deleted file mode 100644 index 7b185786..00000000 --- a/tests/update.js +++ /dev/null @@ -1,67 +0,0 @@ - - test( "Testing update method", function(){ - - Q.html('\ -
\ - \ - '); - - var slider = $('.slider'), - input = $('.input'); - - slider.noUiSlider({ - range: { - 'min': 30, - 'max': 980 - } - ,start: [-3000, 1000] - ,connect: true - ,serialization: { - lower: [ - new Link({ - target: input - }) - ], - upper: [ - new Link({ - target: "newInput" - }) - ], - format: { - encoder: function( value ){ - return 3 * Number(value); - }, - decoder: function( value ){ - return Number(value) / 3; - }, - mark: '|', - decimals: 4 - } - } - }); - - deepEqual( slider.val(), ['90|0000', '1000|0000'], 'Slider handles encoder values.' ); - equal( Q.find('input[name="newInput"]').val(), '1000|0000', 'Input has proper value.'); - - equal( Q.find('input').length, 2 ); - - slider.noUiSlider({ - range: { - 'min': -50, - 'max': 6000 - }, - start: [ -40, 1500 ], - serialization: { - format: { - mark: '.', - decimals: 2 - } - } - }, true); - - equal( Q.find('input').length, 1, 'Hidden input is gone.' ); - equal( Q.find('input.input').val(), '90|0000', "Input didn't update." ); - - deepEqual( slider.val(), ['-40.00', '1500.00'], 'Slider has new settings.' ); - - }); diff --git a/tests/wnumb_basic.js b/tests/wnumb_basic.js new file mode 100644 index 00000000..4acfa0b3 --- /dev/null +++ b/tests/wnumb_basic.js @@ -0,0 +1,66 @@ + + test( "Serialization and formatting options", function(){ + + Q.html('\ +
\ + \ + \ + \ + '); + + $("#slider").noUiSlider({ + start: [ 0, 0.002 ], + connect: true, + behaviour: 'drag', + range: { + 'min': [ 0.001 ], + 'max': [ 0.003 ] + }, + format: wNumb({ + decimals: 5 + }) + }); + + $("#slider").Link('lower').to('-inline-
', function( value, handle, slider ){ + ok( $("#slider")[0] === slider[0] ); + ok( handle.hasClass('noUi-handle') ); + }, wNumb({ + decimals: 6, + prefix: '#', + negativeBefore: '!', + negative: '' + })); + + $("#slider").Link('lower').to($("#inputChange"), null, wNumb({ + mark: '.', + thousand: ',', + prefix: '$', + postfix: ' p.p.', + decimals: 3, + negativeBefore: '-', + negative: '', + encoder: function( a ){ + return a * 1E7; + }, + decoder: function( a ){ + return a / 1E7; + } + })); + + $("#slider").Link('upper').to($("#inputVal"), 'val'); + + $("#inputChange").val( "15000" ).trigger('change'); + + equal( $("#inputChange").val(), "$15,000.000 p.p.", "Testing interpretation of improper data" ); + + $("#inputVal").val( 3 ); + + equal( $("#inputVal").val(), "3" ); + + $("#inputVal").trigger("change"); + + equal ( $("#inputVal").val(), "3", "Input doesn't respond to change events." ); + + deepEqual( $("#slider").val(), ["0.00150", "0.00200"] ); + + }); diff --git a/wnumb b/wnumb new file mode 160000 index 00000000..2bf0af44 --- /dev/null +++ b/wnumb @@ -0,0 +1 @@ +Subproject commit 2bf0af44c1e0ef316cc913236682ca4ef7ead548