1/*! nouislider - 8.5.1 - 2016-04-24 16:00:29 */ 2 3(function (factory) { 4 5 if ( typeof define === 'function' && define.amd ) { 6 7 // AMD. Register as an anonymous module. 8 define([], factory); 9 10 } else if ( typeof exports === 'object' ) { 11 12 // Node/CommonJS 13 module.exports = factory(); 14 15 } else { 16 17 // Browser globals 18 window.noUiSlider = factory(); 19 } 20 21}(function( ){ 22 23 'use strict'; 24 25 26 // Removes duplicates from an array. 27 function unique(array) { 28 return array.filter(function(a){ 29 return !this[a] ? this[a] = true : false; 30 }, {}); 31 } 32 33 // Round a value to the closest 'to'. 34 function closest ( value, to ) { 35 return Math.round(value / to) * to; 36 } 37 38 // Current position of an element relative to the document. 39 function offset ( elem ) { 40 41 var rect = elem.getBoundingClientRect(), 42 doc = elem.ownerDocument, 43 docElem = doc.documentElement, 44 pageOffset = getPageOffset(); 45 46 // getBoundingClientRect contains left scroll in Chrome on Android. 47 // I haven't found a feature detection that proves this. Worst case 48 // scenario on mis-match: the 'tap' feature on horizontal sliders breaks. 49 if ( /webkit.*Chrome.*Mobile/i.test(navigator.userAgent) ) { 50 pageOffset.x = 0; 51 } 52 53 return { 54 top: rect.top + pageOffset.y - docElem.clientTop, 55 left: rect.left + pageOffset.x - docElem.clientLeft 56 }; 57 } 58 59 // Checks whether a value is numerical. 60 function isNumeric ( a ) { 61 return typeof a === 'number' && !isNaN( a ) && isFinite( a ); 62 } 63 64 // Sets a class and removes it after [duration] ms. 65 function addClassFor ( element, className, duration ) { 66 addClass(element, className); 67 setTimeout(function(){ 68 removeClass(element, className); 69 }, duration); 70 } 71 72 // Limits a value to 0 - 100 73 function limit ( a ) { 74 return Math.max(Math.min(a, 100), 0); 75 } 76 77 // Wraps a variable as an array, if it isn't one yet. 78 function asArray ( a ) { 79 return Array.isArray(a) ? a : [a]; 80 } 81 82 // Counts decimals 83 function countDecimals ( numStr ) { 84 var pieces = numStr.split("."); 85 return pieces.length > 1 ? pieces[1].length : 0; 86 } 87 88 // http://youmightnotneedjquery.com/#add_class 89 function addClass ( el, className ) { 90 if ( el.classList ) { 91 el.classList.add(className); 92 } else { 93 el.className += ' ' + className; 94 } 95 } 96 97 // http://youmightnotneedjquery.com/#remove_class 98 function removeClass ( el, className ) { 99 if ( el.classList ) { 100 el.classList.remove(className); 101 } else { 102 el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); 103 } 104 } 105 106 // https://plainjs.com/javascript/attributes/adding-removing-and-testing-for-classes-9/ 107 function hasClass ( el, className ) { 108 return el.classList ? el.classList.contains(className) : new RegExp('\\b' + className + '\\b').test(el.className); 109 } 110 111 // https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes 112 function getPageOffset ( ) { 113 114 var supportPageOffset = window.pageXOffset !== undefined, 115 isCSS1Compat = ((document.compatMode || "") === "CSS1Compat"), 116 x = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft, 117 y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop; 118 119 return { 120 x: x, 121 y: y 122 }; 123 } 124 125 // we provide a function to compute constants instead 126 // of accessing window.* as soon as the module needs it 127 // so that we do not compute anything if not needed 128 function getActions ( ) { 129 130 // Determine the events to bind. IE11 implements pointerEvents without 131 // a prefix, which breaks compatibility with the IE10 implementation. 132 return window.navigator.pointerEnabled ? { 133 start: 'pointerdown', 134 move: 'pointermove', 135 end: 'pointerup' 136 } : window.navigator.msPointerEnabled ? { 137 start: 'MSPointerDown', 138 move: 'MSPointerMove', 139 end: 'MSPointerUp' 140 } : { 141 start: 'mousedown touchstart', 142 move: 'mousemove touchmove', 143 end: 'mouseup touchend' 144 }; 145 } 146 147 148// Value calculation 149 150 // Determine the size of a sub-range in relation to a full range. 151 function subRangeRatio ( pa, pb ) { 152 return (100 / (pb - pa)); 153 } 154 155 // (percentage) How many percent is this value of this range? 156 function fromPercentage ( range, value ) { 157 return (value * 100) / ( range[1] - range[0] ); 158 } 159 160 // (percentage) Where is this value on this range? 161 function toPercentage ( range, value ) { 162 return fromPercentage( range, range[0] < 0 ? 163 value + Math.abs(range[0]) : 164 value - range[0] ); 165 } 166 167 // (value) How much is this percentage on this range? 168 function isPercentage ( range, value ) { 169 return ((value * ( range[1] - range[0] )) / 100) + range[0]; 170 } 171 172 173// Range conversion 174 175 function getJ ( value, arr ) { 176 177 var j = 1; 178 179 while ( value >= arr[j] ){ 180 j += 1; 181 } 182 183 return j; 184 } 185 186 // (percentage) Input a value, find where, on a scale of 0-100, it applies. 187 function toStepping ( xVal, xPct, value ) { 188 189 if ( value >= xVal.slice(-1)[0] ){ 190 return 100; 191 } 192 193 var j = getJ( value, xVal ), va, vb, pa, pb; 194 195 va = xVal[j-1]; 196 vb = xVal[j]; 197 pa = xPct[j-1]; 198 pb = xPct[j]; 199 200 return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb)); 201 } 202 203 // (value) Input a percentage, find where it is on the specified range. 204 function fromStepping ( xVal, xPct, value ) { 205 206 // There is no range group that fits 100 207 if ( value >= 100 ){ 208 return xVal.slice(-1)[0]; 209 } 210 211 var j = getJ( value, xPct ), va, vb, pa, pb; 212 213 va = xVal[j-1]; 214 vb = xVal[j]; 215 pa = xPct[j-1]; 216 pb = xPct[j]; 217 218 return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb)); 219 } 220 221 // (percentage) Get the step that applies at a certain value. 222 function getStep ( xPct, xSteps, snap, value ) { 223 224 if ( value === 100 ) { 225 return value; 226 } 227 228 var j = getJ( value, xPct ), a, b; 229 230 // If 'snap' is set, steps are used as fixed points on the slider. 231 if ( snap ) { 232 233 a = xPct[j-1]; 234 b = xPct[j]; 235 236 // Find the closest position, a or b. 237 if ((value - a) > ((b-a)/2)){ 238 return b; 239 } 240 241 return a; 242 } 243 244 if ( !xSteps[j-1] ){ 245 return value; 246 } 247 248 return xPct[j-1] + closest( 249 value - xPct[j-1], 250 xSteps[j-1] 251 ); 252 } 253 254 255// Entry parsing 256 257 function handleEntryPoint ( index, value, that ) { 258 259 var percentage; 260 261 // Wrap numerical input in an array. 262 if ( typeof value === "number" ) { 263 value = [value]; 264 } 265
266 // Reject any invalid input, by testing whether value is an array. 267 if ( Object.prototype.toString.call( value ) !== '[object Array]' ){ 268 throw new Error("noUiSlider: 'range' contains invalid value."); 269 } 270 271 // Covert min/max syntax to 0 and 100. 272 if ( index === 'min' ) { 273 percentage = 0; 274 } else if ( index === 'max' ) { 275 percentage = 100; 276 } else { 277 percentage = parseFloat( index ); 278 } 279 280 // Check for correct input. 281 if ( !isNumeric( percentage ) || !isNumeric( value[0] ) ) { 282 throw new Error("noUiSlider: 'range' value isn't numeric."); 283 } 284 285 // Store values. 286 that.xPct.push( percentage ); 287 that.xVal.push( value[0] ); 288 289 // NaN will evaluate to false too, but to keep 290 // logging clear, set step explicitly. Make sure 291 // not to override the 'step' setting with false. 292 if ( !percentage ) { 293 if ( !isNaN( value[1] ) ) { 294 that.xSteps[0] = value[1]; 295 } 296 } else { 297 that.xSteps.push( isNaN(value[1]) ? false : value[1] ); 298 } 299 } 300 301 function handleStepPoint ( i, n, that ) { 302 303 // Ignore 'false' stepping. 304 if ( !n ) { 305 return true; 306 } 307 308 // Factor to range ratio 309 that.xSteps[i] = fromPercentage([ 310 that.xVal[i] 311 ,that.xVal[i+1] 312 ], n) / subRangeRatio ( 313 that.xPct[i], 314 that.xPct[i+1] ); 315 } 316 317 318// Interface 319 320 // The interface to Spectrum handles all direction-based 321 // conversions, so the above values are unaware. 322 323 function Spectrum ( entry, snap, direction, singleStep ) { 324 325 this.xPct = []; 326 this.xVal = []; 327 this.xSteps = [ singleStep || false ]; 328 this.xNumSteps = [ false ]; 329 330 this.snap = snap; 331 this.direction = direction; 332 333 var index, ordered = [ /* [0, 'min'], [1, '50%'], [2, 'max'] */ ]; 334 335 // Map the object keys to an array. 336 for ( index in entry ) { 337 if ( entry.hasOwnProperty(index) ) { 338 ordered.push([entry[index], index]); 339 } 340 } 341 342 // Sort all entries by value (numeric sort). 343 if ( ordered.length && typeof ordered[0][0] === "object" ) { 344 ordered.sort(function(a, b) { return a[0][0] - b[0][0]; }); 345 } else { 346 ordered.sort(function(a, b) { return a[0] - b[0]; }); 347 } 348 349 350 // Convert all entries to subranges. 351 for ( index = 0; index < ordered.length; index++ ) { 352 handleEntryPoint(ordered[index][1], ordered[index][0], this); 353 } 354 355 // Store the actual step values. 356 // xSteps is sorted in the same order as xPct and xVal. 357 this.xNumSteps = this.xSteps.slice(0); 358 359 // Convert all numeric steps to the percentage of the subrange they represent. 360 for ( index = 0; index < this.xNumSteps.length; index++ ) { 361 handleStepPoint(index, this.xNumSteps[index], this); 362 } 363 } 364 365 Spectrum.prototype.getMargin = function ( value ) { 366 return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false; 367 }; 368 369 Spectrum.prototype.toStepping = function ( value ) { 370 371 value = toStepping( this.xVal, this.xPct, value ); 372 373 // Invert the value if this is a right-to-left slider. 374 if ( this.direction ) { 375 value = 100 - value; 376 } 377 378 return value; 379 }; 380 381 Spectrum.prototype.fromStepping = function ( value ) { 382 383 // Invert the value if this is a right-to-left slider. 384 if ( this.direction ) { 385 value = 100 - value; 386 } 387 388 return fromStepping( this.xVal, this.xPct, value ); 389 }; 390 391 Spectrum.prototype.getStep = function ( value ) { 392 393 // Find the proper step for rtl sliders by search in inverse direction. 394 // Fixes issue #262. 395 if ( this.direction ) { 396 value = 100 - value; 397 } 398 399 value = getStep(this.xPct, this.xSteps, this.snap, value ); 400 401 if ( this.direction ) { 402 value = 100 - value; 403 } 404 405 return value; 406 }; 407 408 Spectrum.prototype.getApplicableStep = function ( value ) { 409 410 // If the value is 100%, return the negative step twice. 411 var j = getJ(value, this.xPct), offset = value === 100 ? 2 : 1; 412 return [this.xNumSteps[j-2], this.xVal[j-offset], this.xNumSteps[j-offset]]; 413 }; 414 415 // Outside testing 416 Spectrum.prototype.convert = function ( value ) { 417 return this.getStep(this.toStepping(value)); 418 }; 419 420/* Every input option is tested and parsed. This'll prevent 421 endless validation in internal methods. These tests are 422 structured with an item for every option available. An 423 option can be marked as required by setting the 'r' flag. 424 The testing function is provided with three arguments: 425 - The provided value for the option; 426 - A reference to the options object; 427 - The name for the option; 428 429 The testing function returns false when an error is detected, 430 or true when everything is OK. It can also modify the option 431 object, to make sure all values can be correctly looped elsewhere. */ 432 433 var defaultFormatter = { 'to': function( value ){ 434 return value !== undefined && value.toFixed(2); 435 }, 'from': Number }; 436 437 function testStep ( parsed, entry ) { 438 439 if ( !isNumeric( entry ) ) { 440 throw new Error("noUiSlider: 'step' is not numeric."); 441 } 442 443 // The step option can still be used to set stepping 444 // for linear sliders. Overwritten if set in 'range'. 445 parsed.singleStep = entry; 446 } 447 448 function testRange ( parsed, entry ) { 449 450 // Filter incorrect input. 451 if ( typeof entry !== 'object' || Array.isArray(entry) ) { 452 throw new Error("noUiSlider: 'range' is not an object."); 453 } 454 455 // Catch missing start or end. 456 if ( entry.min === undefined || entry.max === undefined ) { 457 throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'."); 458 } 459 460 // Catch equal start or end. 461 if ( entry.min === entry.max ) { 462 throw new Error("noUiSlider: 'range' 'min' and 'max' cannot be equal."); 463 } 464 465 parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.dir, parsed.singleStep); 466 } 467 468 function testStart ( parsed, entry ) { 469 470 entry = asArray(entry); 471 472 // Validate input. Values aren't tested, as the public .val method 473 // will always provide a valid location. 474 if ( !Array.isArray( entry ) || !entry.length || entry.length > 2 ) { 475 throw new Error("noUiSlider: 'start' option is incorrect."); 476 } 477 478 // Store the number of handles. 479 parsed.handles = entry.length; 480
481 // When the slider is initialized, the .val method will 482 // be called with the start options. 483 parsed.start = entry; 484 } 485 486 function testSnap ( parsed, entry ) { 487 488 // Enforce 100% stepping within subranges. 489 parsed.snap = entry; 490 491 if ( typeof entry !== 'boolean' ){ 492 throw new Error("noUiSlider: 'snap' option must be a boolean."); 493 } 494 } 495 496 function testAnimate ( parsed, entry ) { 497 498 // Enforce 100% stepping within subranges. 499 parsed.animate = entry; 500 501 if ( typeof entry !== 'boolean' ){ 502 throw new Error("noUiSlider: 'animate' option must be a boolean."); 503 } 504 } 505 506 function testAnimationDuration ( parsed, entry ) { 507 508 parsed.animationDuration = entry; 509 510 if ( typeof entry !== 'number' ){ 511 throw new Error("noUiSlider: 'animationDuration' option must be a number."); 512 } 513 } 514 515 function testConnect ( parsed, entry ) { 516 517 if ( entry === 'lower' && parsed.handles === 1 ) { 518 parsed.connect = 1; 519 } else if ( entry === 'upper' && parsed.handles === 1 ) { 520 parsed.connect = 2; 521 } else if ( entry === true && parsed.handles === 2 ) { 522 parsed.connect = 3; 523 } else if ( entry === false ) { 524 parsed.connect = 0; 525 } else { 526 throw new Error("noUiSlider: 'connect' option doesn't match handle count."); 527 } 528 } 529 530 function testOrientation ( parsed, entry ) { 531 532 // Set orientation to an a numerical value for easy 533 // array selection. 534 switch ( entry ){ 535 case 'horizontal': 536 parsed.ort = 0; 537 break; 538 case 'vertical': 539 parsed.ort = 1; 540 break; 541 default: 542 throw new Error("noUiSlider: 'orientation' option is invalid."); 543 } 544 } 545 546 function testMargin ( parsed, entry ) { 547 548 if ( !isNumeric(entry) ){ 549 throw new Error("noUiSlider: 'margin' option must be numeric."); 550 } 551 552 // Issue #582 553 if ( entry === 0 ) { 554 return; 555 } 556 557 parsed.margin = parsed.spectrum.getMargin(entry); 558 559 if ( !parsed.margin ) { 560 throw new Error("noUiSlider: 'margin' option is only supported on linear sliders."); 561 } 562 } 563 564 function testLimit ( parsed, entry ) { 565 566 if ( !isNumeric(entry) ){ 567 throw new Error("noUiSlider: 'limit' option must be numeric."); 568 } 569 570 parsed.limit = parsed.spectrum.getMargin(entry); 571 572 if ( !parsed.limit ) { 573 throw new Error("noUiSlider: 'limit' option is only supported on linear sliders."); 574 } 575 } 576 577 function testDirection ( parsed, entry ) { 578 579 // Set direction as a numerical value for easy parsing. 580 // Invert connection for RTL sliders, so that the proper 581 // handles get the connect/background classes. 582 switch ( entry ) { 583 case 'ltr': 584 parsed.dir = 0; 585 break; 586 case 'rtl': 587 parsed.dir = 1; 588 parsed.connect = [0,2,1,3][parsed.connect]; 589 break; 590 default: 591 throw new Error("noUiSlider: 'direction' option was not recognized."); 592 } 593 } 594 595 function testBehaviour ( parsed, entry ) { 596 597 // Make sure the input is a string. 598 if ( typeof entry !== 'string' ) { 599 throw new Error("noUiSlider: 'behaviour' must be a string containing options."); 600 } 601 602 // Check if the string contains any keywords. 603 // None are required. 604 var tap = entry.indexOf('tap') >= 0, 605 drag = entry.indexOf('drag') >= 0, 606 fixed = entry.indexOf('fixed') >= 0, 607 snap = entry.indexOf('snap') >= 0, 608 hover = entry.indexOf('hover') >= 0; 609 610 // Fix #472 611 if ( drag && !parsed.connect ) { 612 throw new Error("noUiSlider: 'drag' behaviour must be used with 'connect': true."); 613 } 614 615 parsed.events = { 616 tap: tap || snap, 617 drag: drag, 618 fixed: fixed, 619 snap: snap, 620 hover: hover 621 }; 622 } 623 624 function testTooltips ( parsed, entry ) { 625 626 var i; 627 628 if ( entry === false ) { 629 return; 630 } else if ( entry === true ) { 631 632 parsed.tooltips = []; 633 634 for ( i = 0; i < parsed.handles; i++ ) { 635 parsed.tooltips.push(true); 636 } 637 638 } else { 639 640 parsed.tooltips = asArray(entry); 641 642 if ( parsed.tooltips.length !== parsed.handles ) { 643 throw new Error("noUiSlider: must pass a formatter for all handles."); 644 } 645 646 parsed.tooltips.forEach(function(formatter){ 647 if ( typeof formatter !== 'boolean' && (typeof formatter !== 'object' || typeof formatter.to !== 'function') ) { 648 throw new Error("noUiSlider: 'tooltips' must be passed a formatter or 'false'."); 649 } 650 }); 651 } 652 } 653 654 function testFormat ( parsed, entry ) { 655 656 parsed.format = entry; 657 658 // Any object with a to and from method is supported. 659 if ( typeof entry.to === 'function' && typeof entry.from === 'function' ) { 660 return true; 661 } 662 663 throw new Error("noUiSlider: 'format' requires 'to' and 'from' methods."); 664 } 665 666 function testCssPrefix ( parsed, entry ) { 667 668 if ( entry !== undefined && typeof entry !== 'string' && entry !== false ) { 669 throw new Error("noUiSlider: 'cssPrefix' must be a string or `false`."); 670 } 671 672 parsed.cssPrefix = entry; 673 } 674
675 function testCssClasses ( parsed, entry ) { 676 677 if ( entry !== undefined && typeof entry !== 'object' ) { 678 throw new Error("noUiSlider: 'cssClasses' must be an object."); 679 } 680 681 if ( typeof parsed.cssPrefix === 'string' ) { 682 parsed.cssClasses = {}; 683 684 for ( var key in entry ) { 685 if ( !entry.hasOwnProperty(key) ) { continue; } 686 687 parsed.cssClasses[key] = parsed.cssPrefix + entry[key]; 688 } 689 } else { 690 parsed.cssClasses = entry; 691 } 692 } 693 694 // Test all developer settings and parse to assumption-safe values. 695 function testOptions ( options ) { 696 697 // To prove a fix for #537, freeze options here. 698 // If the object is modified, an error will be thrown. 699 // Object.freeze(options); 700 701 var parsed = { 702 margin: 0, 703 limit: 0, 704 animate: true, 705 animationDuration: 300, 706 format: defaultFormatter 707 }, tests; 708 709 // Tests are executed in the order they are presented here. 710 tests = { 711 'step': { r: false, t: testStep }, 712 'start': { r: true, t: testStart }, 713 'connect': { r: true, t: testConnect }, 714 'direction': { r: true, t: testDirection }, 715 'snap': { r: false, t: testSnap }, 716 'animate': { r: false, t: testAnimate }, 717 'animationDuration': { r: false, t: testAnimationDuration }, 718 'range': { r: true, t: testRange }, 719 'orientation': { r: false, t: testOrientation }, 720 'margin': { r: false, t: testMargin }, 721 'limit': { r: false, t: testLimit }, 722 'behaviour': { r: true, t: testBehaviour }, 723 'format': { r: false, t: testFormat }, 724 'tooltips': { r: false, t: testTooltips }, 725 'cssPrefix': { r: false, t: testCssPrefix },
726 'cssClasses': { r: false, t: testCssClasses } 727 }; 728 729 var defaults = { 730 'connect': false, 731 'direction': 'ltr', 732 'behaviour': 'tap', 733 'orientation': 'horizontal', 734 'cssPrefix' : 'noUi-', 735 'cssClasses': { 736 target: 'target', 737 base: 'base', 738 origin: 'origin', 739 handle: 'handle', 740 handleLower: 'handle-lower', 741 handleUpper: 'handle-upper', 742 horizontal: 'horizontal', 743 vertical: 'vertical', 744 background: 'background', 745 connect: 'connect', 746 ltr: 'ltr', 747 rtl: 'rtl', 748 draggable: 'draggable', 749 drag: 'state-drag', 750 tap: 'state-tap', 751 active: 'active', 752 stacking: 'stacking', 753 tooltip: 'tooltip', 754 pips: 'pips', 755 pipsHorizontal: 'pips-horizontal', 756 pipsVertical: 'pips-vertical', 757 marker: 'marker', 758 markerHorizontal: 'marker-horizontal', 759 markerVertical: 'marker-vertical', 760 markerNormal: 'marker-normal', 761 markerLarge: 'marker-large', 762 markerSub: 'marker-sub', 763 value: 'value', 764 valueHorizontal: 'value-horizontal', 765 valueVertical: 'value-vertical', 766 valueNormal: 'value-normal', 767 valueLarge: 'value-large', 768 valueSub: 'value-sub' 769 } 770 }; 771 772 // Run all options through a testing mechanism to ensure correct 773 // input. It should be noted that options might get modified to 774 // be handled properly. E.g. wrapping integers in arrays. 775 Object.keys(tests).forEach(function( name ){ 776 777 // If the option isn't set, but it is required, throw an error. 778 if ( options[name] === undefined && defaults[name] === undefined ) { 779 780 if ( tests[name].r ) { 781 throw new Error("noUiSlider: '" + name + "' is required."); 782 } 783 784 return true; 785 } 786 787 tests[name].t( parsed, options[name] === undefined ? defaults[name] : options[name] ); 788 }); 789 790 // Forward pips options
791 parsed.pips = options.pips; 792 793 // Pre-define the styles. 794 parsed.style = parsed.ort ? 'top' : 'left'; 795 796 return parsed; 797 } 798 799 800function closure ( target, options, originalOptions ){ 801 var 802 actions = getActions( ), 803 // All variables local to 'closure' are prefixed with 'scope_' 804 scope_Target = target, 805 scope_Locations = [-1, -1], 806 scope_Base, 807 scope_Handles, 808 scope_Spectrum = options.spectrum, 809 scope_Values = [], 810 scope_Events = {}, 811 scope_Self; 812 813 814 // Delimit proposed values for handle positions. 815 function getPositions ( a, b, delimit ) { 816 817 // Add movement to current position. 818 var c = a + b[0], d = a + b[1]; 819 820 // Only alter the other position on drag, 821 // not on standard sliding. 822 if ( delimit ) { 823 if ( c < 0 ) { 824 d += Math.abs(c); 825 } 826 if ( d > 100 ) { 827 c -= ( d - 100 ); 828 } 829 830 // Limit values to 0 and 100. 831 return [limit(c), limit(d)]; 832 } 833 834 return [c,d]; 835 } 836 837 // Provide a clean event with standardized offset values. 838 function fixEvent ( e, pageOffset ) { 839 840 // Prevent scrolling and panning on touch events, while 841 // attempting to slide. The tap event also depends on this. 842 e.preventDefault(); 843 844 // Filter the event to register the type, which can be 845 // touch, mouse or pointer. Offset changes need to be 846 // made on an event specific basis. 847 var touch = e.type.indexOf('touch') === 0, 848 mouse = e.type.indexOf('mouse') === 0, 849 pointer = e.type.indexOf('pointer') === 0, 850 x,y, event = e; 851 852 // IE10 implemented pointer events with a prefix; 853 if ( e.type.indexOf('MSPointer') === 0 ) { 854 pointer = true; 855 } 856 857 if ( touch ) { 858 // noUiSlider supports one movement at a time, 859 // so we can select the first 'changedTouch'. 860 x = e.changedTouches[0].pageX; 861 y = e.changedTouches[0].pageY; 862 } 863 864 pageOffset = pageOffset || getPageOffset(); 865 866 if ( mouse || pointer ) { 867 x = e.clientX + pageOffset.x; 868 y = e.clientY + pageOffset.y; 869 } 870 871 event.pageOffset = pageOffset; 872 event.points = [x, y]; 873 event.cursor = mouse || pointer; // Fix #435 874 875 return event; 876 } 877 878 // Append a handle to the base. 879 function addHandle ( direction, index ) { 880 881 var origin = document.createElement('div'), 882 handle = document.createElement('div'), 883 classModifier = [options.cssClasses.handleLower, options.cssClasses.handleUpper]; 884 885 if ( direction ) { 886 classModifier.reverse(); 887 } 888 889 addClass(handle, options.cssClasses.handle); 890 addClass(handle, classModifier[index]); 891 892 addClass(origin, options.cssClasses.origin); 893 origin.appendChild(handle); 894 895 return origin; 896 } 897 898 // Add the proper connection classes. 899 function addConnection ( connect, target, handles ) { 900 901 // Apply the required connection classes to the elements 902 // that need them. Some classes are made up for several 903 // segments listed in the class list, to allow easy 904 // renaming and provide a minor compression benefit. 905 switch ( connect ) { 906 case 1: addClass(target, options.cssClasses.connect); 907 addClass(handles[0], options.cssClasses.background); 908 break; 909 case 3: addClass(handles[1], options.cssClasses.background); 910 /* falls through */ 911 case 2: addClass(handles[0], options.cssClasses.connect); 912 /* falls through */ 913 case 0: addClass(target, options.cssClasses.background); 914 break; 915 } 916 } 917 918 // Add handles to the slider base. 919 function addHandles ( nrHandles, direction, base ) { 920 921 var index, handles = []; 922 923 // Append handles. 924 for ( index = 0; index < nrHandles; index += 1 ) { 925 926 // Keep a list of all added handles. 927 handles.push( base.appendChild(addHandle( direction, index )) ); 928 } 929 930 return handles; 931 } 932 933 // Initialize a single slider. 934 function addSlider ( direction, orientation, target ) { 935 936 // Apply classes and data to the target. 937 addClass(target, options.cssClasses.target); 938 939 if ( direction === 0 ) { 940 addClass(target, options.cssClasses.ltr); 941 } else { 942 addClass(target, options.cssClasses.rtl); 943 } 944 945 if ( orientation === 0 ) {
946 addClass(target, options.cssClasses.horizontal); 947 } else { 948 addClass(target, options.cssClasses.vertical); 949 } 950 951 var div = document.createElement('div'); 952 addClass(div, options.cssClasses.base); 953 target.appendChild(div); 954 return div; 955 } 956 957 958 function addTooltip ( handle, index ) { 959 960 if ( !options.tooltips[index] ) { 961 return false; 962 } 963 964 var element = document.createElement('div'); 965 element.className = options.cssClasses.tooltip; 966 return handle.firstChild.appendChild(element); 967 } 968 969 // The tooltips option is a shorthand for using the 'update' event. 970 function tooltips ( ) { 971 972 if ( options.dir ) { 973 options.tooltips.reverse(); 974 } 975 976 // Tooltips are added with options.tooltips in original order. 977 var tips = scope_Handles.map(addTooltip); 978 979 if ( options.dir ) { 980 tips.reverse(); 981 options.tooltips.reverse(); 982 } 983 984 bindEvent('update', function(f, o, r) { 985 if ( tips[o] ) { 986 tips[o].innerHTML = options.tooltips[o] === true ? f[o] : options.tooltips[o].to(r[o]); 987 } 988 }); 989 } 990 991 992 function getGroup ( mode, values, stepped ) { 993 994 // Use the range. 995 if ( mode === 'range' || mode === 'steps' ) { 996 return scope_Spectrum.xVal; 997 } 998 999 if ( mode === 'count' ) { 1000 1001 // Divide 0 - 100 in 'count' parts. 1002 var spread = ( 100 / (values-1) ), v, i = 0; 1003 values = []; 1004 1005 // List these parts and have them handled as 'positions'. 1006 while ((v=i++*spread) <= 100 ) { 1007 values.push(v); 1008 } 1009 1010 mode = 'positions'; 1011 } 1012 1013 if ( mode === 'positions' ) { 1014 1015 // Map all percentages to on-range values. 1016 return values.map(function( value ){ 1017 return scope_Spectrum.fromStepping( stepped ? scope_Spectrum.getStep( value ) : value ); 1018 }); 1019 } 1020 1021 if ( mode === 'values' ) { 1022 1023 // If the value must be stepped, it needs to be converted to a percentage first. 1024 if ( stepped ) { 1025 1026 return values.map(function( value ){ 1027 1028 // Convert to percentage, apply step, return to value. 1029 return scope_Spectrum.fromStepping( scope_Spectrum.getStep( scope_Spectrum.toStepping( value ) ) ); 1030 }); 1031 1032 } 1033 1034 // Otherwise, we can simply use the values. 1035 return values; 1036 } 1037 } 1038 1039 function generateSpread ( density, mode, group ) { 1040 1041 function safeIncrement(value, increment) { 1042 // Avoid floating point variance by dropping the smallest decimal places. 1043 return (value + increment).toFixed(7) / 1; 1044 } 1045 1046 var originalSpectrumDirection = scope_Spectrum.direction, 1047 indexes = {}, 1048 firstInRange = scope_Spectrum.xVal[0], 1049 lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length-1], 1050 ignoreFirst = false, 1051 ignoreLast = false, 1052 prevPct = 0; 1053 1054 // This function loops the spectrum in an ltr linear fashion, 1055 // while the toStepping method is direction aware. Trick it into 1056 // believing it is ltr. 1057 scope_Spectrum.direction = 0; 1058 1059 // Create a copy of the group, sort it and filter away all duplicates. 1060 group = unique(group.slice().sort(function(a, b){ return a - b; })); 1061 1062 // Make sure the range starts with the first element. 1063 if ( group[0] !== firstInRange ) { 1064 group.unshift(firstInRange); 1065 ignoreFirst = true; 1066 } 1067 1068 // Likewise for the last one. 1069 if ( group[group.length - 1] !== lastInRange ) { 1070 group.push(lastInRange); 1071 ignoreLast = true; 1072 } 1073 1074 group.forEach(function ( current, index ) { 1075 1076 // Get the current step and the lower + upper positions. 1077 var step, i, q, 1078 low = current, 1079 high = group[index+1], 1080 newPct, pctDifference, pctPos, type, 1081 steps, realSteps, stepsize; 1082 1083 // When using 'steps' mode, use the provided steps. 1084 // Otherwise, we'll step on to the next subrange. 1085 if ( mode === 'steps' ) { 1086 step = scope_Spectrum.xNumSteps[ index ]; 1087 } 1088 1089 // Default to a 'full' step. 1090 if ( !step ) { 1091 step = high-low; 1092 } 1093 1094 // Low can be 0, so test for false. If high is undefined, 1095 // we are at the last subrange. Index 0 is already handled. 1096 if ( low === false || high === undefined ) { 1097 return; 1098 } 1099 1100 // Find all steps in the subrange. 1101 for ( i = low; i <= high; i = safeIncrement(i, step) ) { 1102 1103 // Get the percentage value for the current step, 1104 // calculate the size for the subrange. 1105 newPct = scope_Spectrum.toStepping( i ); 1106 pctDifference = newPct - prevPct; 1107 1108 steps = pctDifference / density; 1109 realSteps = Math.round(steps); 1110 1111 // This ratio represents the ammount of percentage-space a point indicates. 1112 // For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-devided. 1113 // Round the percentage offset to an even number, then divide by two 1114 // to spread the offset on both sides of the range. 1115 stepsize = pctDifference/realSteps; 1116 1117 // Divide all points evenly, adding the correct number to this subrange. 1118 // Run up to <= so that 100% gets a point, event if ignoreLast is set. 1119 for ( q = 1; q <= realSteps; q += 1 ) { 1120 1121 // The ratio between the rounded value and the actual size might be ~1% off. 1122 // Correct the percentage offset by the number of points 1123 // per subrange. density = 1 will result in 100 points on the 1124 // full range, 2 for 50, 4 for 25, etc. 1125 pctPos = prevPct + ( q * stepsize ); 1126 indexes[pctPos.toFixed(5)] = ['x', 0]; 1127 } 1128 1129 // Determine the point type. 1130 type = (group.indexOf(i) > -1) ? 1 : ( mode === 'steps' ? 2 : 0 ); 1131 1132 // Enforce the 'ignoreFirst' option by overwriting the type for 0. 1133 if ( !index && ignoreFirst ) { 1134 type = 0; 1135 } 1136 1137 if ( !(i === high && ignoreLast)) { 1138 // Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value. 1139 indexes[newPct.toFixed(5)] = [i, type]; 1140 } 1141 1142 // Update the percentage count. 1143 prevPct = newPct; 1144 } 1145 }); 1146 1147 // Reset the spectrum. 1148 scope_Spectrum.direction = originalSpectrumDirection; 1149 1150 return indexes; 1151 } 1152 1153 function addMarking ( spread, filterFunc, formatter ) { 1154 1155 var element = document.createElement('div'), 1156 out = '', 1157 valueSizeClasses = [
1158 options.cssClasses.valueNormal, 1159 options.cssClasses.valueLarge, 1160 options.cssClasses.valueSub 1161 ], 1162 markerSizeClasses = [ 1163 options.cssClasses.markerNormal, 1164 options.cssClasses.markerLarge, 1165 options.cssClasses.markerSub 1166 ], 1167 valueOrientationClasses = [ 1168 options.cssClasses.valueHorizontal, 1169 options.cssClasses.valueVertical 1170 ], 1171 markerOrientationClasses = [ 1172 options.cssClasses.markerHorizontal, 1173 options.cssClasses.markerVertical 1174 ]; 1175 1176 addClass(element, options.cssClasses.pips); 1177 addClass(element, options.ort === 0 ? options.cssClasses.pipsHorizontal : options.cssClasses.pipsVertical); 1178 1179 function getClasses( type, source ){ 1180 var a = source === options.cssClasses.value, 1181 orientationClasses = a ? valueOrientationClasses : markerOrientationClasses, 1182 sizeClasses = a ? valueSizeClasses : markerSizeClasses; 1183 1184 return source + ' ' + orientationClasses[options.ort] + ' ' + sizeClasses[type]; 1185 } 1186 1187 function getTags( offset, source, values ) { 1188 return 'class="' + getClasses(values[1], source) + '" style="' + options.style + ': ' + offset + '%"'; 1189 } 1190 1191 function addSpread ( offset, values ){ 1192 1193 if ( scope_Spectrum.direction ) { 1194 offset = 100 - offset; 1195 } 1196 1197 // Apply the filter function, if it is set. 1198 values[1] = (values[1] && filterFunc) ? filterFunc(values[0], values[1]) : values[1]; 1199 1200 // Add a marker for every point 1201 out += '<div ' + getTags(offset, options.cssClasses.marker, values) + '></div>'; 1202 1203 // Values are only appended for points marked '1' or '2'. 1204 if ( values[1] ) { 1205 out += '<div ' + getTags(offset, options.cssClasses.value, values) + '>' + formatter.to(values[0]) + '</div>'; 1206 } 1207 } 1208 1209 // Append all points. 1210 Object.keys(spread).forEach(function(a){ 1211 addSpread(a, spread[a]); 1212 }); 1213 1214 element.innerHTML = out; 1215 1216 return element; 1217 } 1218 1219 function pips ( grid ) { 1220 1221 var mode = grid.mode, 1222 density = grid.density || 1, 1223 filter = grid.filter || false, 1224 values = grid.values || false, 1225 stepped = grid.stepped || false, 1226 group = getGroup( mode, values, stepped ), 1227 spread = generateSpread( density, mode, group ), 1228 format = grid.format || { 1229 to: Math.round 1230 }; 1231 1232 return scope_Target.appendChild(addMarking( 1233 spread, 1234 filter, 1235 format 1236 )); 1237 } 1238 1239 1240 // Shorthand for base dimensions. 1241 function baseSize ( ) { 1242 var rect = scope_Base.getBoundingClientRect(), alt = 'offset' + ['Width', 'Height'][options.ort]; 1243 return options.ort === 0 ? (rect.width||scope_Base[alt]) : (rect.height||scope_Base[alt]); 1244 } 1245 1246 // External event handling 1247 function fireEvent ( event, handleNumber, tap ) { 1248 1249 var i; 1250 1251 // During initialization, do not fire events. 1252 for ( i = 0; i < options.handles; i++ ) { 1253 if ( scope_Locations[i] === -1 ) { 1254 return; 1255 } 1256 } 1257 1258 if ( handleNumber !== undefined && options.handles !== 1 ) { 1259 handleNumber = Math.abs(handleNumber - options.dir); 1260 } 1261 1262 Object.keys(scope_Events).forEach(function( targetEvent ) { 1263 1264 var eventType = targetEvent.split('.')[0]; 1265 1266 if ( event === eventType ) { 1267 scope_Events[targetEvent].forEach(function( callback ) { 1268 1269 callback.call( 1270 // Use the slider public API as the scope ('this') 1271 scope_Self, 1272 // Return values as array, so arg_1[arg_2] is always valid. 1273 asArray(valueGet()), 1274 // Handle index, 0 or 1 1275 handleNumber, 1276 // Unformatted slider values 1277 asArray(inSliderOrder(Array.prototype.slice.call(scope_Values))), 1278 // Event is fired by tap, true or false 1279 tap || false, 1280 // Left offset of the handle, in relation to the slider 1281 scope_Locations 1282 ); 1283 }); 1284 } 1285 }); 1286 } 1287 1288 // Returns the input array, respecting the slider direction configuration. 1289 function inSliderOrder ( values ) { 1290 1291 // If only one handle is used, return a single value. 1292 if ( values.length === 1 ){ 1293 return values[0]; 1294 } 1295 1296 if ( options.dir ) { 1297 return values.reverse(); 1298 } 1299 1300 return values; 1301 } 1302 1303 1304 // Handler for attaching events trough a proxy. 1305 function attach ( events, element, callback, data ) { 1306 1307 // This function can be used to 'filter' events to the slider. 1308 // element is a node, not a nodeList 1309 1310 var method = function ( e ){ 1311 1312 if ( scope_Target.hasAttribute('disabled') ) { 1313 return false; 1314 } 1315 1316 // Stop if an active 'tap' transition is taking place.
1317 if ( hasClass(scope_Target, options.cssClasses.tap) ) { 1318 return false; 1319 } 1320 1321 e = fixEvent(e, data.pageOffset); 1322 1323 // Ignore right or middle clicks on start #454 1324 if ( events === actions.start && e.buttons !== undefined && e.buttons > 1 ) { 1325 return false; 1326 } 1327 1328 // Ignore right or middle clicks on start #454 1329 if ( data.hover && e.buttons ) { 1330 return false; 1331 } 1332 1333 e.calcPoint = e.points[ options.ort ]; 1334 1335 // Call the event handler with the event [ and additional data ]. 1336 callback ( e, data ); 1337 1338 }, methods = []; 1339 1340 // Bind a closure on the target for every event type. 1341 events.split(' ').forEach(function( eventName ){ 1342 element.addEventListener(eventName, method, false); 1343 methods.push([eventName, method]); 1344 }); 1345 1346 return methods; 1347 } 1348 1349 // Handle movement on document for handle and range drag. 1350 function move ( event, data ) { 1351 1352 // Fix #498 1353 // Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty). 1354 // https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero 1355 // IE9 has .buttons and .which zero on mousemove. 1356 // Firefox breaks the spec MDN defines. 1357 if ( navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0 ) { 1358 return end(event, data); 1359 } 1360 1361 var handles = data.handles || scope_Handles, positions, state = false, 1362 proposal = ((event.calcPoint - data.start) * 100) / data.baseSize, 1363 handleNumber = handles[0] === scope_Handles[0] ? 0 : 1, i; 1364 1365 // Calculate relative positions for the handles. 1366 positions = getPositions( proposal, data.positions, handles.length > 1); 1367 1368 state = setHandle ( handles[0], positions[handleNumber], handles.length === 1 ); 1369 1370 if ( handles.length > 1 ) { 1371 1372 state = setHandle ( handles[1], positions[handleNumber?0:1], false ) || state; 1373 1374 if ( state ) { 1375 // fire for both handles 1376 for ( i = 0; i < data.handles.length; i++ ) { 1377 fireEvent('slide', i); 1378 } 1379 } 1380 } else if ( state ) { 1381 // Fire for a single handle 1382 fireEvent('slide', handleNumber); 1383 } 1384 } 1385 1386 // Unbind move events on document, call callbacks. 1387 function end ( event, data ) { 1388 1389 // The handle is no longer active, so remove the class. 1390 var active = scope_Base.querySelector( '.' + options.cssClasses.active ), 1391 handleNumber = data.handles[0] === scope_Handles[0] ? 0 : 1; 1392 1393 if ( active !== null ) { 1394 removeClass(active, options.cssClasses.active); 1395 } 1396 1397 // Remove cursor styles and text-selection events bound to the body. 1398 if ( event.cursor ) { 1399 document.body.style.cursor = ''; 1400 document.body.removeEventListener('selectstart', document.body.noUiListener); 1401 } 1402 1403 var d = document.documentElement; 1404 1405 // Unbind the move and end events, which are added on 'start'. 1406 d.noUiListeners.forEach(function( c ) { 1407 d.removeEventListener(c[0], c[1]); 1408 }); 1409 1410 // Remove dragging class. 1411 removeClass(scope_Target, options.cssClasses.drag); 1412 1413 // Fire the change and set events. 1414 fireEvent('set', handleNumber); 1415 fireEvent('change', handleNumber); 1416 1417 // If this is a standard handle movement, fire the end event. 1418 if ( data.handleNumber !== undefined ) { 1419 fireEvent('end', data.handleNumber); 1420 } 1421 } 1422 1423 // Fire 'end' when a mouse or pen leaves the document. 1424 function documentLeave ( event, data ) { 1425 if ( event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null ){ 1426 end ( event, data ); 1427 } 1428 } 1429 1430 // Bind move events on document. 1431 function start ( event, data ) { 1432 1433 var d = document.documentElement; 1434 1435 // Mark the handle as 'active' so it can be styled. 1436 if ( data.handles.length === 1 ) { 1437 // Support 'disabled' handles 1438 if ( data.handles[0].hasAttribute('disabled') ) { 1439 return false; 1440 } 1441 1442 addClass(data.handles[0].children[0], options.cssClasses.active); 1443 } 1444 1445 // Fix #551, where a handle gets selected instead of dragged. 1446 event.preventDefault(); 1447 1448 // A drag should never propagate up to the 'tap' event. 1449 event.stopPropagation(); 1450 1451 // Attach the move and end events. 1452 var moveEvent = attach(actions.move, d, move, { 1453 start: event.calcPoint, 1454 baseSize: baseSize(), 1455 pageOffset: event.pageOffset, 1456 handles: data.handles, 1457 handleNumber: data.handleNumber, 1458 buttonsProperty: event.buttons, 1459 positions: [ 1460 scope_Locations[0], 1461 scope_Locations[scope_Handles.length - 1] 1462 ] 1463 }), endEvent = attach(actions.end, d, end, { 1464 handles: data.handles, 1465 handleNumber: data.handleNumber 1466 }); 1467 1468 var outEvent = attach("mouseout", d, documentLeave, { 1469 handles: data.handles, 1470 handleNumber: data.handleNumber 1471 }); 1472 1473 d.noUiListeners = moveEvent.concat(endEvent, outEvent); 1474 1475 // Text selection isn't an issue on touch devices, 1476 // so adding cursor styles can be skipped. 1477 if ( event.cursor ) { 1478 1479 // Prevent the 'I' cursor and extend the range-drag cursor. 1480 document.body.style.cursor = getComputedStyle(event.target).cursor; 1481 1482 // Mark the target with a dragging state. 1483 if ( scope_Handles.length > 1 ) {
1484 addClass(scope_Target, options.cssClasses.drag); 1485 } 1486 1487 var f = function(){ 1488 return false; 1489 }; 1490 1491 document.body.noUiListener = f; 1492 1493 // Prevent text selection when dragging the handles. 1494 document.body.addEventListener('selectstart', f, false); 1495 } 1496 1497 if ( data.handleNumber !== undefined ) { 1498 fireEvent('start', data.handleNumber); 1499 } 1500 } 1501 1502 // Move closest handle to tapped location. 1503 function tap ( event ) { 1504 1505 var location = event.calcPoint, total = 0, handleNumber, to; 1506 1507 // The tap event shouldn't propagate up and cause 'edge' to run. 1508 event.stopPropagation(); 1509 1510 // Add up the handle offsets. 1511 scope_Handles.forEach(function(a){ 1512 total += offset(a)[ options.style ]; 1513 }); 1514 1515 // Find the handle closest to the tapped position. 1516 handleNumber = ( location < total/2 || scope_Handles.length === 1 ) ? 0 : 1; 1517 1518 // Check if handler is not disablet if yes set number to the next handler 1519 if (scope_Handles[handleNumber].hasAttribute('disabled')) { 1520 handleNumber = handleNumber ? 0 : 1; 1521 } 1522 1523 location -= offset(scope_Base)[ options.style ]; 1524 1525 // Calculate the new position. 1526 to = ( location * 100 ) / baseSize(); 1527 1528 if ( !options.events.snap ) { 1529 // Flag the slider as it is now in a transitional state. 1530 // Transition takes a configurable amount of ms (default 300). Re-enable the slider after that. 1531 addClassFor( scope_Target, options.cssClasses.tap, options.animationDuration ); 1532 } 1533 1534 // Support 'disabled' handles 1535 if ( scope_Handles[handleNumber].hasAttribute('disabled') ) { 1536 return false; 1537 } 1538 1539 // Find the closest handle and calculate the tapped point. 1540 // The set handle to the new position. 1541 setHandle( scope_Handles[handleNumber], to ); 1542 1543 fireEvent('slide', handleNumber, true); 1544 fireEvent('set', handleNumber, true); 1545 fireEvent('change', handleNumber, true); 1546 1547 if ( options.events.snap ) { 1548 start(event, { handles: [scope_Handles[handleNumber]] }); 1549 } 1550 } 1551 1552 // Fires a 'hover' event for a hovered mouse/pen position. 1553 function hover ( event ) { 1554 1555 var location = event.calcPoint - offset(scope_Base)[ options.style ], 1556 to = scope_Spectrum.getStep(( location * 100 ) / baseSize()), 1557 value = scope_Spectrum.fromStepping( to ); 1558 1559 Object.keys(scope_Events).forEach(function( targetEvent ) { 1560 if ( 'hover' === targetEvent.split('.')[0] ) { 1561 scope_Events[targetEvent].forEach(function( callback ) { 1562 callback.call( scope_Self, value ); 1563 }); 1564 } 1565 }); 1566 } 1567 1568 // Attach events to several slider parts. 1569 function events ( behaviour ) { 1570 1571 // Attach the standard drag event to the handles. 1572 if ( !behaviour.fixed ) { 1573 1574 scope_Handles.forEach(function( handle, index ){ 1575 1576 // These events are only bound to the visual handle 1577 // element, not the 'real' origin element. 1578 attach ( actions.start, handle.children[0], start, { 1579 handles: [ handle ], 1580 handleNumber: index 1581 }); 1582 }); 1583 } 1584 1585 // Attach the tap event to the slider base. 1586 if ( behaviour.tap ) { 1587 1588 attach ( actions.start, scope_Base, tap, { 1589 handles: scope_Handles 1590 }); 1591 } 1592 1593 // Fire hover events 1594 if ( behaviour.hover ) { 1595 attach ( actions.move, scope_Base, hover, { hover: true } ); 1596 } 1597 1598 // Make the range draggable. 1599 if ( behaviour.drag ){ 1600 1601 var drag = [scope_Base.querySelector( '.' + options.cssClasses.connect )]; 1602 addClass(drag[0], options.cssClasses.draggable); 1603 1604 // When the range is fixed, the entire range can 1605 // be dragged by the handles. The handle in the first 1606 // origin will propagate the start event upward, 1607 // but it needs to be bound manually on the other. 1608 if ( behaviour.fixed ) { 1609 drag.push(scope_Handles[(drag[0] === scope_Handles[0] ? 1 : 0)].children[0]); 1610 } 1611 1612 drag.forEach(function( element ) { 1613 attach ( actions.start, element, start, { 1614 handles: scope_Handles 1615 }); 1616 }); 1617 } 1618 } 1619 1620 1621 // Test suggested values and apply margin, step. 1622 function setHandle ( handle, to, noLimitOption ) { 1623 1624 var trigger = handle !== scope_Handles[0] ? 1 : 0, 1625 lowerMargin = scope_Locations[0] + options.margin, 1626 upperMargin = scope_Locations[1] - options.margin, 1627 lowerLimit = scope_Locations[0] + options.limit, 1628 upperLimit = scope_Locations[1] - options.limit; 1629 1630 // For sliders with multiple handles, 1631 // limit movement to the other handle. 1632 // Apply the margin option by adding it to the handle positions. 1633 if ( scope_Handles.length > 1 ) { 1634 to = trigger ? Math.max( to, lowerMargin ) : Math.min( to, upperMargin ); 1635 } 1636 1637 // The limit option has the opposite effect, limiting handles to a 1638 // maximum distance from another. Limit must be > 0, as otherwise 1639 // handles would be unmoveable. 'noLimitOption' is set to 'false' 1640 // for the .val() method, except for pass 4/4. 1641 if ( noLimitOption !== false && options.limit && scope_Handles.length > 1 ) { 1642 to = trigger ? Math.min ( to, lowerLimit ) : Math.max( to, upperLimit ); 1643 } 1644 1645 // Handle the step option. 1646 to = scope_Spectrum.getStep( to ); 1647 1648 // Limit percentage to the 0 - 100 range 1649 to = limit(to); 1650 1651 // Return false if handle can't move 1652 if ( to === scope_Locations[trigger] ) { 1653 return false; 1654 } 1655 1656 // Set the handle to the new position. 1657 // Use requestAnimationFrame for efficient painting. 1658 // No significant effect in Chrome, Edge sees dramatic 1659 // performace improvements. 1660 if ( window.requestAnimationFrame ) { 1661 window.requestAnimationFrame(function(){ 1662 handle.style[options.style] = to + '%'; 1663 }); 1664 } else { 1665 handle.style[options.style] = to + '%'; 1666 } 1667 1668 // Force proper handle stacking 1669 if ( !handle.previousSibling ) {
1670 removeClass(handle, options.cssClasses.stacking); 1671 if ( to > 50 ) { 1672 addClass(handle, options.cssClasses.stacking); 1673 } 1674 } 1675 1676 // Update locations. 1677 scope_Locations[trigger] = to; 1678 1679 // Convert the value to the slider stepping/range. 1680 scope_Values[trigger] = scope_Spectrum.fromStepping( to ); 1681 1682 fireEvent('update', trigger); 1683 1684 return true; 1685 } 1686 1687 // Loop values from value method and apply them. 1688 function setValues ( count, values ) { 1689 1690 var i, trigger, to; 1691 1692 // With the limit option, we'll need another limiting pass. 1693 if ( options.limit ) { 1694 count += 1; 1695 } 1696 1697 // If there are multiple handles to be set run the setting 1698 // mechanism twice for the first handle, to make sure it 1699 // can be bounced of the second one properly. 1700 for ( i = 0; i < count; i += 1 ) { 1701 1702 trigger = i%2; 1703 1704 // Get the current argument from the array. 1705 to = values[trigger]; 1706 1707 // Setting with null indicates an 'ignore'. 1708 // Inputting 'false' is invalid. 1709 if ( to !== null && to !== false ) { 1710 1711 // If a formatted number was passed, attemt to decode it. 1712 if ( typeof to === 'number' ) { 1713 to = String(to); 1714 } 1715 1716 to = options.format.from( to ); 1717
1718 // Request an update for all links if the value was invalid. 1719 // Do so too if setting the handle fails. 1720 if ( to === false || isNaN(to) || setHandle( scope_Handles[trigger], scope_Spectrum.toStepping( to ), i === (3 - options.dir) ) === false ) { 1721 fireEvent('update', trigger); 1722 } 1723 } 1724 } 1725 } 1726 1727 // Set the slider value. 1728 function valueSet ( input, fireSetEvent ) { 1729 1730 var count, values = asArray( input ), i; 1731 1732 // Event fires by default 1733 fireSetEvent = (fireSetEvent === undefined ? true : !!fireSetEvent); 1734 1735 // The RTL settings is implemented by reversing the front-end, 1736 // internal mechanisms are the same. 1737 if ( options.dir && options.handles > 1 ) { 1738 values.reverse(); 1739 } 1740 1741 // Animation is optional. 1742 // Make sure the initial values where set before using animated placement. 1743 if ( options.animate && scope_Locations[0] !== -1 ) { 1744 addClassFor( scope_Target, options.cssClasses.tap, options.animationDuration ); 1745 } 1746 1747 // Determine how often to set the handles. 1748 count = scope_Handles.length > 1 ? 3 : 1; 1749 1750 if ( values.length === 1 ) { 1751 count = 1; 1752 } 1753 1754 setValues ( count, values ); 1755 1756 // Fire the 'set' event for both handles. 1757 for ( i = 0; i < scope_Handles.length; i++ ) { 1758 1759 // Fire the event only for handles that received a new value, as per #579 1760 if ( values[i] !== null && fireSetEvent ) { 1761 fireEvent('set', i); 1762 } 1763 } 1764 } 1765 1766 // Get the slider value. 1767 function valueGet ( ) { 1768 1769 var i, retour = []; 1770 1771 // Get the value from all handles. 1772 for ( i = 0; i < options.handles; i += 1 ){ 1773 retour[i] = options.format.to( scope_Values[i] ); 1774 } 1775 1776 return inSliderOrder( retour ); 1777 } 1778 1779 // Removes classes from the root and empties it. 1780 function destroy ( ) { 1781 1782 for ( var key in options.cssClasses ) { 1783 if ( !options.cssClasses.hasOwnProperty(key) ) { continue; } 1784 removeClass(scope_Target, options.cssClasses[key]); 1785 } 1786 1787 while (scope_Target.firstChild) { 1788 scope_Target.removeChild(scope_Target.firstChild); 1789 } 1790 1791 delete scope_Target.noUiSlider; 1792 } 1793 1794 // Get the current step size for the slider. 1795 function getCurrentStep ( ) { 1796 1797 // Check all locations, map them to their stepping point. 1798 // Get the step point, then find it in the input list. 1799 var retour = scope_Locations.map(function( location, index ){ 1800 1801 var step = scope_Spectrum.getApplicableStep( location ), 1802 1803 // As per #391, the comparison for the decrement step can have some rounding issues. 1804 // Round the value to the precision used in the step. 1805 stepDecimals = countDecimals(String(step[2])), 1806 1807 // Get the current numeric value 1808 value = scope_Values[index], 1809 1810 // To move the slider 'one step up', the current step value needs to be added. 1811 // Use null if we are at the maximum slider value. 1812 increment = location === 100 ? null : step[2], 1813 1814 // Going 'one step down' might put the slider in a different sub-range, so we 1815 // need to switch between the current or the previous step. 1816 prev = Number((value - step[2]).toFixed(stepDecimals)), 1817 1818 // If the value fits the step, return the current step value. Otherwise, use the 1819 // previous step. Return null if the slider is at its minimum value. 1820 decrement = location === 0 ? null : (prev >= step[1]) ? step[2] : (step[0] || false); 1821 1822 return [decrement, increment]; 1823 }); 1824 1825 // Return values in the proper order. 1826 return inSliderOrder( retour ); 1827 } 1828 1829 // Attach an event to this slider, possibly including a namespace 1830 function bindEvent ( namespacedEvent, callback ) { 1831 scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || []; 1832 scope_Events[namespacedEvent].push(callback); 1833 1834 // If the event bound is 'update,' fire it immediately for all handles. 1835 if ( namespacedEvent.split('.')[0] === 'update' ) { 1836 scope_Handles.forEach(function(a, index){ 1837 fireEvent('update', index); 1838 }); 1839 } 1840 } 1841 1842 // Undo attachment of event 1843 function removeEvent ( namespacedEvent ) { 1844 1845 var event = namespacedEvent && namespacedEvent.split('.')[0], 1846 namespace = event && namespacedEvent.substring(event.length); 1847
1848 Object.keys(scope_Events).forEach(function( bind ){ 1849 1850 var tEvent = bind.split('.')[0], 1851 tNamespace = bind.substring(tEvent.length); 1852 1853 if ( (!event || event === tEvent) && (!namespace || namespace === tNamespace) ) { 1854 delete scope_Events[bind]; 1855 } 1856 }); 1857 } 1858 1859 // Updateable: margin, limit, step, range, animate, snap 1860 function updateOptions ( optionsToUpdate, fireSetEvent ) { 1861 1862 // Spectrum is created using the range, snap, direction and step options. 1863 // 'snap' and 'step' can be updated, 'direction' cannot, due to event binding. 1864 // If 'snap' and 'step' are not passed, they should remain unchanged. 1865 var v = valueGet(), newOptions = testOptions({ 1866 start: [0, 0], 1867 margin: optionsToUpdate.margin, 1868 limit: optionsToUpdate.limit, 1869 step: optionsToUpdate.step === undefined ? options.singleStep : optionsToUpdate.step, 1870 range: optionsToUpdate.range, 1871 animate: optionsToUpdate.animate, 1872 snap: optionsToUpdate.snap === undefined ? options.snap : optionsToUpdate.snap 1873 }); 1874 1875 ['margin', 'limit', 'range', 'animate'].forEach(function(name){ 1876 1877 // Only change options that we're actually passed to update. 1878 if ( optionsToUpdate[name] !== undefined ) { 1879 options[name] = optionsToUpdate[name]; 1880 } 1881 }); 1882 1883 // Save current spectrum direction as testOptions in testRange call 1884 // doesn't rely on current direction 1885 newOptions.spectrum.direction = scope_Spectrum.direction; 1886 scope_Spectrum = newOptions.spectrum; 1887 1888 // Invalidate the current positioning so valueSet forces an update. 1889 scope_Locations = [-1, -1]; 1890 valueSet(optionsToUpdate.start || v, fireSetEvent); 1891 } 1892 1893 1894 // Throw an error if the slider was already initialized. 1895 if ( scope_Target.noUiSlider ) { 1896 throw new Error('Slider was already initialized.'); 1897 } 1898 1899 // Create the base element, initialise HTML and set classes. 1900 // Add handles and links. 1901 scope_Base = addSlider( options.dir, options.ort, scope_Target ); 1902 scope_Handles = addHandles( options.handles, options.dir, scope_Base ); 1903 1904 // Set the connect classes. 1905 addConnection ( options.connect, scope_Target, scope_Handles ); 1906 1907 if ( options.pips ) { 1908 pips(options.pips); 1909 } 1910 1911 if ( options.tooltips ) { 1912 tooltips(); 1913 } 1914 1915 scope_Self = { 1916 destroy: destroy, 1917 steps: getCurrentStep, 1918 on: bindEvent, 1919 off: removeEvent, 1920 get: valueGet, 1921 set: valueSet, 1922 updateOptions: updateOptions, 1923 options: originalOptions, // Issue #600 1924 target: scope_Target, // Issue #597 1925 pips: pips // Issue #594 1926 }; 1927 1928 // Attach user events. 1929 events( options.events ); 1930 1931 return scope_Self; 1932 1933} 1934 1935 1936 // Run the standard initializer 1937 function initialize ( target, originalOptions ) { 1938 1939 if ( !target.nodeName ) { 1940 throw new Error('noUiSlider.create requires a single element.'); 1941 } 1942 1943 // Test the options and create the slider environment; 1944 var options = testOptions( originalOptions, target ), 1945 slider = closure( target, options, originalOptions ); 1946 1947 // Use the public value method to set the start values. 1948 slider.set(options.start); 1949 1950 target.noUiSlider = slider; 1951 return slider; 1952 } 1953 1954 // Use an object instead of a function for future expansibility; 1955 return { 1956 create: initialize 1957 }; 1958 1959}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.